update SpamAssassin to 4.0.1

generated by make update-upstream

Signed-off-by: Stoiko Ivanov <s.ivanov@proxmox.com>
This commit is contained in:
Stoiko Ivanov 2024-05-31 17:16:08 +02:00
parent 10a34462c9
commit f887dfc0c7
156 changed files with 10187 additions and 9428 deletions

View File

@ -1,4 +1,4 @@
Copyright (C) 2022 The Apache Software Foundation Copyright (C) 2024 The Apache Software Foundation
Project Management Committee (PMC): Project Management Committee (PMC):
@ -299,6 +299,8 @@ Patch submitters:
- Tomasz Ostrowski, <tometzky(at)batory.org.pl>: perl 5.005 support. - Tomasz Ostrowski, <tometzky(at)batory.org.pl>: perl 5.005 support.
- Kent Oyer, <kent(at)mxguardian.net>: multiple patches: Razor2 docs, encoding, and HTML parsing.
- Henning P. Schmiedehausen, <hps(at)intermeta.de> <henning(at)apache.org>: - Henning P. Schmiedehausen, <hps(at)intermeta.de> <henning(at)apache.org>:
adding ? to shell globs. adding ? to shell globs.
@ -355,8 +357,8 @@ Patch submitters:
by Mike Quinn: source of the original SpamAssassin logo. by Mike Quinn: source of the original SpamAssassin logo.
If your name is not here, and you've submitted a patch that was included, If your name is not here, and you've submitted a patch that was included,
it's just an oversight. Please mail me at <jm /at/ jmason.org> and I'll add it's just an oversight. Please mail the PMC at <private(at)spamassassin.apache.org>
you to the list. and we'll add you to the list.
ASF Sponsorship: ASF Sponsorship:

File diff suppressed because it is too large Load Diff

View File

@ -123,6 +123,11 @@ lib/Mail/SpamAssassin/Plugin/WLBLEval.pm
lib/Mail/SpamAssassin/Plugin/WelcomeListSubject.pm lib/Mail/SpamAssassin/Plugin/WelcomeListSubject.pm
lib/Mail/SpamAssassin/PluginHandler.pm lib/Mail/SpamAssassin/PluginHandler.pm
lib/Mail/SpamAssassin/Plugin/URILocalBL.pm lib/Mail/SpamAssassin/Plugin/URILocalBL.pm
lib/Mail/SpamAssassin/Pyzor/Client.pm
lib/Mail/SpamAssassin/Pyzor/Digest/Pieces.pm
lib/Mail/SpamAssassin/Pyzor/Digest/StripHtml.pm
lib/Mail/SpamAssassin/Pyzor/Digest.pm
lib/Mail/SpamAssassin/Pyzor.pm
lib/Mail/SpamAssassin/RegistryBoundaries.pm lib/Mail/SpamAssassin/RegistryBoundaries.pm
lib/Mail/SpamAssassin/Reporter.pm lib/Mail/SpamAssassin/Reporter.pm
lib/Mail/SpamAssassin/SQLBasedAddrList.pm lib/Mail/SpamAssassin/SQLBasedAddrList.pm
@ -152,6 +157,7 @@ rules/v341.pre
rules/v342.pre rules/v342.pre
rules/v343.pre rules/v343.pre
rules/v400.pre rules/v400.pre
rules/v401.pre
rules/20_aux_tlds.cf rules/20_aux_tlds.cf
rules-extras/README.txt rules-extras/README.txt
rules-extras/10_uridnsbl_skip_financial.cf rules-extras/10_uridnsbl_skip_financial.cf
@ -247,6 +253,7 @@ t/autolearn_force.t
t/autolearn_force_fail.t t/autolearn_force_fail.t
t/basic_lint.t t/basic_lint.t
t/basic_lint_net.t t/basic_lint_net.t
t/basic_lint_without_plugins.t
t/basic_lint_without_sandbox.t t/basic_lint_without_sandbox.t
t/basic_meta.t t/basic_meta.t
t/basic_meta2.t t/basic_meta2.t
@ -382,6 +389,7 @@ t/data/nice/orig_ip_hdr.eml
t/data/nice/spf1 t/data/nice/spf1
t/data/nice/spf2 t/data/nice/spf2
t/data/nice/spf3 t/data/nice/spf3
t/data/nice/spf4
t/data/nice/spf3-received-spf t/data/nice/spf3-received-spf
t/data/nice/spf4-received-spf-nofold t/data/nice/spf4-received-spf-nofold
t/data/nice/spf5-received-spf-crlf t/data/nice/spf5-received-spf-crlf
@ -418,9 +426,12 @@ t/data/spam/badmime3.txt
t/data/spam/base64.txt t/data/spam/base64.txt
t/data/spam/bsmtp t/data/spam/bsmtp
t/data/spam/bsmtpnull t/data/spam/bsmtpnull
t/data/spam/decodeshorturl/anchor.eml
t/data/spam/decodeshorturl/base.eml t/data/spam/decodeshorturl/base.eml
t/data/spam/decodeshorturl/base2.eml t/data/spam/decodeshorturl/base2.eml
t/data/spam/decodeshorturl/chain.eml t/data/spam/decodeshorturl/chain.eml
t/data/spam/decodeshorturl/doubleslash.eml
t/data/spam/decodeshorturl/params.eml
t/data/spam/dmarc/nodmarc.eml t/data/spam/dmarc/nodmarc.eml
t/data/spam/dmarc/noneko.eml t/data/spam/dmarc/noneko.eml
t/data/spam/dmarc/quarko.eml t/data/spam/dmarc/quarko.eml
@ -457,11 +468,22 @@ t/data/spam/spf1
t/data/spam/spf2 t/data/spam/spf2
t/data/spam/spf3 t/data/spam/spf3
t/data/spam/unicode1 t/data/spam/unicode1
t/data/spam/unicode2
t/data/spam/urilocalbl_net.eml t/data/spam/urilocalbl_net.eml
t/data/spamc_blank.cf t/data/spamc_blank.cf
t/data/taintcheckplugin.pm t/data/taintcheckplugin.pm
t/data/testplugin.pm t/data/testplugin.pm
t/data/testplugin2.pm t/data/testplugin2.pm
t/data/txrep/0
t/data/txrep/1
t/data/txrep/2
t/data/txrep/3
t/data/txrep/4
t/data/txrep/5
t/data/txrep/6
t/data/txrep/7
t/data/txrep/8
t/data/txrep/9
t/data/validuserplugin.pm t/data/validuserplugin.pm
t/data/welcomelists/action.eff.org t/data/welcomelists/action.eff.org
t/data/welcomelists/amazon_co_uk_ship t/data/welcomelists/amazon_co_uk_ship
@ -505,6 +527,7 @@ t/dkim.t
t/dnsbl.t t/dnsbl.t
t/dnsbl_sc_meta.t t/dnsbl_sc_meta.t
t/dnsbl_subtests.t t/dnsbl_subtests.t
t/dnsplatform.t
t/enable_compat.t t/enable_compat.t
t/extracttext.t t/extracttext.t
t/freemail.t t/freemail.t
@ -519,6 +542,7 @@ t/hashbl.t
t/html_colors.t t/html_colors.t
t/html_obfu.t t/html_obfu.t
t/html_utf8.t t/html_utf8.t
t/html_visibility.t
t/idn_dots.t t/idn_dots.t
t/if_can.t t/if_can.t
t/if_else.t t/if_else.t
@ -583,6 +607,9 @@ t/sa_awl.t
t/sa_awl_welcome_block.t t/sa_awl_welcome_block.t
t/sa_check_spamd.t t/sa_check_spamd.t
t/sa_compile.t t/sa_compile.t
t/sa_txrep.t
t/sa_txrep_sql.t
t/sa_txrep_welcomelist_out.t
t/sha1.t t/sha1.t
t/shortcircuit.t t/shortcircuit.t
t/shortcircuit_before_dns.t t/shortcircuit_before_dns.t
@ -658,6 +685,7 @@ t/uribl.t
t/uribl_all_types.t t/uribl_all_types.t
t/uribl_domains_only.t t/uribl_domains_only.t
t/uribl_ips_only.t t/uribl_ips_only.t
t/uridetail.t
t/urilocalbl.t t/urilocalbl.t
t/utf8.t t/utf8.t
t/util_wrap.t t/util_wrap.t

View File

@ -6,7 +6,6 @@
"dynamic_config" : 1, "dynamic_config" : 1,
"generated_by" : "ExtUtils::MakeMaker version 7.62, CPAN::Meta::Converter version 2.150010", "generated_by" : "ExtUtils::MakeMaker version 7.62, CPAN::Meta::Converter version 2.150010",
"license" : [ "license" : [
"unknown",
"apache_2_0" "apache_2_0"
], ],
"meta-spec" : { "meta-spec" : {
@ -48,7 +47,7 @@
"IO::String" : "0", "IO::String" : "0",
"IP::Country::DB_File" : "0", "IP::Country::DB_File" : "0",
"IP::Country::Fast" : "0", "IP::Country::Fast" : "0",
"LWP::UserAgent" : "0", "LWP::Protocol::https" : "0",
"MIME::Base64" : "0", "MIME::Base64" : "0",
"Mail::DKIM" : "0.37", "Mail::DKIM" : "0.37",
"Mail::DMARC" : "0", "Mail::DMARC" : "0",
@ -67,10 +66,11 @@
"Errno" : "0", "Errno" : "0",
"File::Copy" : "2.02", "File::Copy" : "2.02",
"File::Spec" : "0.8", "File::Spec" : "0.8",
"File::Temp" : "0",
"HTML::Parser" : "3.43", "HTML::Parser" : "3.43",
"IO::Zlib" : "1.04", "IO::Zlib" : "1.04",
"Mail::DKIM" : "0.31", "Mail::DKIM" : "0.31",
"Net::DNS" : "0.69", "Net::DNS" : "1.1",
"NetAddr::IP" : "4.01", "NetAddr::IP" : "4.01",
"Pod::Usage" : "1.1", "Pod::Usage" : "1.1",
"Sys::Hostname" : "0", "Sys::Hostname" : "0",
@ -81,14 +81,14 @@
}, },
"test" : { "test" : {
"recommends" : { "recommends" : {
"Net::DNS::Nameserver" : "0" "Devel::Cycle" : "0",
"Net::DNS::Nameserver" : "0",
"Text::Diff" : "0"
}, },
"requires" : { "requires" : {
"Devel::Cycle" : "0",
"Perl::Critic::Policy::Perlsecret" : "0", "Perl::Critic::Policy::Perlsecret" : "0",
"Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict" : "0", "Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict" : "0",
"Test::More" : "0", "Test::Simple" : "1.302067"
"Text::Diff" : "0"
} }
} }
}, },
@ -104,6 +104,6 @@
}, },
"x_MailingList" : "http://wiki.apache.org/spamassassin/MailingLists" "x_MailingList" : "http://wiki.apache.org/spamassassin/MailingLists"
}, },
"version" : "4.000000", "version" : "4.000001",
"x_serialization_backend" : "JSON::PP version 4.06" "x_serialization_backend" : "JSON::PP version 4.06"
} }

View File

@ -3,17 +3,15 @@ abstract: 'Apache SpamAssassin is an extensible email filter which is used to id
author: author:
- 'The Apache SpamAssassin Project <dev@spamassassin.apache.org>' - 'The Apache SpamAssassin Project <dev@spamassassin.apache.org>'
build_requires: build_requires:
Devel::Cycle: '0'
ExtUtils::MakeMaker: '6.64' ExtUtils::MakeMaker: '6.64'
Perl::Critic::Policy::Perlsecret: '0' Perl::Critic::Policy::Perlsecret: '0'
Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict: '0' Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict: '0'
Test::More: '0' Test::Simple: '1.302067'
Text::Diff: '0'
configure_requires: configure_requires:
ExtUtils::MakeMaker: '6.64' ExtUtils::MakeMaker: '6.64'
dynamic_config: 1 dynamic_config: 1
generated_by: 'ExtUtils::MakeMaker version 7.62, CPAN::Meta::Converter version 2.150010' generated_by: 'ExtUtils::MakeMaker version 7.62, CPAN::Meta::Converter version 2.150010'
license: unknown license: apache
meta-spec: meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: '1.4' version: '1.4'
@ -38,7 +36,7 @@ recommends:
IO::String: '0' IO::String: '0'
IP::Country::DB_File: '0' IP::Country::DB_File: '0'
IP::Country::Fast: '0' IP::Country::Fast: '0'
LWP::UserAgent: '0' LWP::Protocol::https: '0'
MIME::Base64: '0' MIME::Base64: '0'
Mail::DKIM: '0.37' Mail::DKIM: '0.37'
Mail::DMARC: '0' Mail::DMARC: '0'
@ -56,10 +54,11 @@ requires:
Errno: '0' Errno: '0'
File::Copy: '2.02' File::Copy: '2.02'
File::Spec: '0.8' File::Spec: '0.8'
File::Temp: '0'
HTML::Parser: '3.43' HTML::Parser: '3.43'
IO::Zlib: '1.04' IO::Zlib: '1.04'
Mail::DKIM: '0.31' Mail::DKIM: '0.31'
Net::DNS: '0.69' Net::DNS: '1.1'
NetAddr::IP: '4.01' NetAddr::IP: '4.01'
Pod::Usage: '1.1' Pod::Usage: '1.1'
Sys::Hostname: '0' Sys::Hostname: '0'
@ -71,5 +70,5 @@ resources:
homepage: https://spamassassin.apache.org/ homepage: https://spamassassin.apache.org/
license: http://www.apache.org/licenses/LICENSE-2.0.html license: http://www.apache.org/licenses/LICENSE-2.0.html
repository: http://svn.apache.org/repos/asf/spamassassin/ repository: http://svn.apache.org/repos/asf/spamassassin/
version: '4.000000' version: '4.000001'
x_serialization_backend: 'CPAN::Meta::YAML version 0.018' x_serialization_backend: 'CPAN::Meta::YAML version 0.018'

View File

@ -175,12 +175,13 @@ my %makefile = (
'PREREQ_PM' => { 'PREREQ_PM' => {
'File::Spec' => 0.8, # older versions lack some routines we need 'File::Spec' => 0.8, # older versions lack some routines we need
'File::Copy' => 2.02, # this version is shipped with 5.005_03, the oldest version known to work 'File::Copy' => 2.02, # this version is shipped with 5.005_03, the oldest version known to work
'File::Temp' => 0, # core module, dependency not needed, here for testing purposes, see bug 8089
'Pod::Usage' => 1.10, # all versions prior to this do seem to be buggy 'Pod::Usage' => 1.10, # all versions prior to this do seem to be buggy
'HTML::Parser' => 3.43, # the HTML code is based on this parser, older versions have utf-8 bugs 'HTML::Parser' => 3.43, # the HTML code is based on this parser, older versions have utf-8 bugs
'Archive::Tar' => 1.23, # for sa-update 'Archive::Tar' => 1.23, # for sa-update
'IO::Zlib' => 1.04, # for sa-update 'IO::Zlib' => 1.04, # for sa-update
'Mail::DKIM' => 0.31, 'Mail::DKIM' => 0.31,
'Net::DNS' => 0.69, 'Net::DNS' => 1.10,
'NetAddr::IP' => 4.010, 'NetAddr::IP' => 4.010,
'Sys::Hostname' => 0, 'Sys::Hostname' => 0,
'Time::HiRes' => 0, 'Time::HiRes' => 0,
@ -198,15 +199,15 @@ my %makefile = (
'ExtUtils::MakeMaker' => MIN_MAKEMAKER_VERSION, 'ExtUtils::MakeMaker' => MIN_MAKEMAKER_VERSION,
}, },
# The modules that are not core that are used in default tests # The modules that are not core or that require a minimum version that are used in default tests
'TEST_REQUIRES' => { 'TEST_REQUIRES' => {
'Devel::Cycle' => 0, 'Test::Simple' => 1.302067,
'Test::More' => 0,
'Text::Diff' => 0,
'Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict' => 0, 'Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict' => 0,
'Perl::Critic::Policy::Perlsecret' => 0, 'Perl::Critic::Policy::Perlsecret' => 0,
}, },
'LICENSE' => 'apache_2_0',
'dist' => { 'dist' => {
COMPRESS => 'gzip -9f', COMPRESS => 'gzip -9f',
SUFFIX => '.gz', SUFFIX => '.gz',
@ -349,11 +350,9 @@ $makefile{META_MERGE} = {
'meta-spec' => { 'meta-spec' => {
version => '2', version => '2',
url => 'http://search.cpan.org/perldoc?CPAN::Meta::Spec', url => 'https://metacpan.org/pod/CPAN::Meta::Spec',
}, },
license => 'apache_2_0',
resources => { resources => {
license => 'http://www.apache.org/licenses/LICENSE-2.0.html', license => 'http://www.apache.org/licenses/LICENSE-2.0.html',
homepage => 'https://spamassassin.apache.org/', homepage => 'https://spamassassin.apache.org/',
@ -387,7 +386,7 @@ $makefile{META_MERGE} = {
'Mail::DKIM' => 0.37, 'Mail::DKIM' => 0.37,
'DBI' => 0, 'DBI' => 0,
'DBD::SQLite' => 1.59_01, 'DBD::SQLite' => 1.59_01,
'LWP::UserAgent' => 0, 'LWP::Protocol::https' => 0,
'Encode::Detect::Detector' => 0, 'Encode::Detect::Detector' => 0,
'Net::Patricia' => 1.16, 'Net::Patricia' => 1.16,
'Net::CIDR::Lite' => 0, 'Net::CIDR::Lite' => 0,
@ -401,6 +400,8 @@ $makefile{META_MERGE} = {
test => { test => {
recommends => { recommends => {
'Net::DNS::Nameserver' => 0, 'Net::DNS::Nameserver' => 0,
'Devel::Cycle' => 0,
'Text::Diff' => 0,
}, },
}, },
}, },
@ -1161,6 +1162,7 @@ conf__install:
$(PERL) -MFile::Copy -e "copy(q[rules/v342.pre], q[$(B_CONFDIR)/v342.pre]) unless -f q[$(B_CONFDIR)/v342.pre]" $(PERL) -MFile::Copy -e "copy(q[rules/v342.pre], q[$(B_CONFDIR)/v342.pre]) unless -f q[$(B_CONFDIR)/v342.pre]"
$(PERL) -MFile::Copy -e "copy(q[rules/v343.pre], q[$(B_CONFDIR)/v343.pre]) unless -f q[$(B_CONFDIR)/v343.pre]" $(PERL) -MFile::Copy -e "copy(q[rules/v343.pre], q[$(B_CONFDIR)/v343.pre]) unless -f q[$(B_CONFDIR)/v343.pre]"
$(PERL) -MFile::Copy -e "copy(q[rules/v400.pre], q[$(B_CONFDIR)/v400.pre]) unless -f q[$(B_CONFDIR)/v400.pre]" $(PERL) -MFile::Copy -e "copy(q[rules/v400.pre], q[$(B_CONFDIR)/v400.pre]) unless -f q[$(B_CONFDIR)/v400.pre]"
$(PERL) -MFile::Copy -e "copy(q[rules/v401.pre], q[$(B_CONFDIR)/v401.pre]) unless -f q[$(B_CONFDIR)/v401.pre]"
data__install: data__install:
-$(MKPATH) $(B_DATADIR) -$(MKPATH) $(B_DATADIR)

View File

@ -1,3 +1,11 @@
Note for Users Upgrading to SpamAssassin 4.0.1
----------------------------------------------
- Phishstats.info domain has expired;
"phishing_phishstats_feed" and "phishing_phishstats_minscore"
options have been removed from Mail::SpamAssassin::Plugin::Phishing
plugin.
Note for Users Upgrading to SpamAssassin 4.0.0 Note for Users Upgrading to SpamAssassin 4.0.0
---------------------------------------------- ----------------------------------------------
@ -252,7 +260,7 @@ improved throughout.
including internal_networks, trusted_networks, msa_networks, and including internal_networks, trusted_networks, msa_networks, and
uri_local_cidr. uri_local_cidr.
- The HashBL plugin in 342.pre is now enabled by default. - The HashBL plugin in v342.pre is now enabled by default.
- HeaderEval check_for_unique_subject_id() function is deprecated. - HeaderEval check_for_unique_subject_id() function is deprecated.

View File

@ -87,7 +87,7 @@ use Time::HiRes qw(time);
use Cwd; use Cwd;
use Config; use Config;
our $VERSION = "4.000000"; # update after release (same format as perl $]) our $VERSION = "4.000001"; # update after release (same format as perl $])
#our $IS_DEVEL_BUILD = 1; # 1 for devel build #our $IS_DEVEL_BUILD = 1; # 1 for devel build
our $IS_DEVEL_BUILD = 0; # 0 for release versions including rc & pre releases our $IS_DEVEL_BUILD = 0; # 0 for release versions including rc & pre releases
@ -100,18 +100,18 @@ our @ISA = qw();
# SUB_VERSION is now just <yyyy>-<mm>-<dd> # SUB_VERSION is now just <yyyy>-<mm>-<dd>
our $SUB_VERSION = 'svnunknown'; our $SUB_VERSION = 'svnunknown';
if ('$LastChangedDate: 2022-12-14 02:29:30 +0000 (Wed, 14 Dec 2022) $' =~ ':') { if ('$LastChangedDate: 2024-03-26 17:46:03 +1300 (Tue, 26 Mar 2024) $' =~ ':') {
# Subversion keyword "$LastChangedDate: 2022-12-14 02:29:30 +0000 (Wed, 14 Dec 2022) $" has been successfully expanded. # Subversion keyword "$LastChangedDate: 2024-03-26 17:46:03 +1300 (Tue, 26 Mar 2024) $" has been successfully expanded.
# Doesn't happen with automated launchpad builds: # Doesn't happen with automated launchpad builds:
# https://bugs.launchpad.net/launchpad/+bug/780916 # https://bugs.launchpad.net/launchpad/+bug/780916
$SUB_VERSION = (split(/\s+/,'$LastChangedDate: 2022-12-14 02:29:30 +0000 (Wed, 14 Dec 2022) $ updated by SVN'))[1]; $SUB_VERSION = (split(/\s+/,'$LastChangedDate: 2024-03-26 17:46:03 +1300 (Tue, 26 Mar 2024) $ updated by SVN'))[1];
} }
if (defined $IS_DEVEL_BUILD && $IS_DEVEL_BUILD) { if (defined $IS_DEVEL_BUILD && $IS_DEVEL_BUILD) {
if ('$LastChangedRevision: 1905971 $' =~ ':') { if ('$LastChangedRevision: 1916544 $' =~ ':') {
# Subversion keyword "$LastChangedRevision: 1905971 $" has been successfully expanded. # Subversion keyword "$LastChangedRevision: 1916544 $" has been successfully expanded.
push(@EXTRA_VERSION, ('r' . qw{$LastChangedRevision: 1905971 $ updated by SVN}[1])); push(@EXTRA_VERSION, ('r' . qw{$LastChangedRevision: 1916544 $ updated by SVN}[1]));
} else { } else {
push(@EXTRA_VERSION, ('r' . 'svnunknown')); push(@EXTRA_VERSION, ('r' . 'svnunknown'));
} }
@ -173,7 +173,7 @@ our @default_userstate_dir = (
########################################################################### ###########################################################################
=item $t = Mail::SpamAssassin->new( { opt => val, ... } ) =item $t = Mail::SpamAssassin-E<gt>new( { opt =E<gt> val, ... } )
Constructs a new C<Mail::SpamAssassin> object. You may pass a hash Constructs a new C<Mail::SpamAssassin> object. You may pass a hash
reference to the constructor which may contain the following attribute- reference to the constructor which may contain the following attribute-
@ -272,7 +272,7 @@ in advance that some information will not be needed by a caller.
A value of the option can either be a string (a comma-delimited list of tag A value of the option can either be a string (a comma-delimited list of tag
names), or a reference to a list of individual tag names. A caller may provide names), or a reference to a list of individual tag names. A caller may provide
the list in advance, specifying his intention to later collect the information the list in advance, specifying his intention to later collect the information
through $pms->get_tag() calls. If a name of a tag starts with a 'NO' (case through $pms-E<gt>get_tag() calls. If a name of a tag starts with a 'NO' (case
insensitive), it shows that a caller will not be interested in such tag, insensitive), it shows that a caller will not be interested in such tag,
although there is no guarantee it would save any resources, nor that a tag although there is no guarantee it would save any resources, nor that a tag
value will be empty. Currently no built-in tags start with 'NO'. A later value will be empty. Currently no built-in tags start with 'NO'. A later
@ -289,9 +289,9 @@ Not requesting it can save a millisecond or two - it mostly serves to
illustrate the usage of need_tags. illustrate the usage of need_tags.
Example: Example:
need_tags => 'TIMING,noLANGUAGES,RELAYCOUNTRY,ASN,noASNCIDR', need_tags =E<gt> 'TIMING,noLANGUAGES,RELAYCOUNTRY,ASN,noASNCIDR',
or: or:
need_tags => [qw(TIMING noLANGUAGES RELAYCOUNTRY ASN noASNCIDR)], need_tags =E<gt> [qw(TIMING noLANGUAGES RELAYCOUNTRY ASN noASNCIDR)],
=item ignore_site_cf_files =item ignore_site_cf_files
@ -561,7 +561,7 @@ sub parse {
########################################################################### ###########################################################################
=item $status = $f->check ($mail) =item $status = $f-E<gt>check ($mail)
Check a mail, encapsulated in a C<Mail::SpamAssassin::Message> object, Check a mail, encapsulated in a C<Mail::SpamAssassin::Message> object,
to determine if it is spam or not. to determine if it is spam or not.
@ -586,7 +586,7 @@ sub check {
$pms; $pms;
} }
=item $status = $f->check_message_text ($mailtext) =item $status = $f-E<gt>check_message_text ($mailtext)
Check a mail, encapsulated in a plain string C<$mailtext>, to determine if it Check a mail, encapsulated in a plain string C<$mailtext>, to determine if it
is spam or not. is spam or not.
@ -612,7 +612,7 @@ sub check_message_text {
########################################################################### ###########################################################################
=item $status = $f->learn ($mail, $id, $isspam, $forget) =item $status = $f-E<gt>learn ($mail, $id, $isspam, $forget)
Learn from a mail, encapsulated in a C<Mail::SpamAssassin::Message> object. Learn from a mail, encapsulated in a C<Mail::SpamAssassin::Message> object.
@ -663,7 +663,7 @@ sub learn {
########################################################################### ###########################################################################
=item $f->init_learner ( [ { opt => val, ... } ] ) =item $f-E<gt>init_learner ( [ { opt =E<gt> val, ... } ] )
Initialise learning. You may pass the following attribute-value pairs to this Initialise learning. You may pass the following attribute-value pairs to this
method. method.
@ -735,7 +735,7 @@ sub init_learner {
########################################################################### ###########################################################################
=item $f->rebuild_learner_caches ({ opt => val }) =item $f-E<gt>rebuild_learner_caches ({ opt =E<gt> val })
Rebuild any cache databases; should be called after the learning process. Rebuild any cache databases; should be called after the learning process.
Options include: C<verbose>, which will output diagnostics to C<stdout> Options include: C<verbose>, which will output diagnostics to C<stdout>
@ -750,7 +750,7 @@ sub rebuild_learner_caches {
1; 1;
} }
=item $f->finish_learner () =item $f-E<gt>finish_learner ()
Finish learning. Finish learning.
@ -763,7 +763,7 @@ sub finish_learner {
1; 1;
} }
=item $f->dump_bayes_db() =item $f-E<gt>dump_bayes_db()
Dump the contents of the Bayes DB Dump the contents of the Bayes DB
@ -774,7 +774,7 @@ sub dump_bayes_db {
$self->{bayes_scanner}->dump_bayes_db(@opts) if $self->{bayes_scanner}; $self->{bayes_scanner}->dump_bayes_db(@opts) if $self->{bayes_scanner};
} }
=item $f->signal_user_changed ( [ { opt => val, ... } ] ) =item $f-E<gt>signal_user_changed ( [ { opt =E<gt> val, ... } ] )
Signals that the current user has changed (possibly using C<setuid>), meaning Signals that the current user has changed (possibly using C<setuid>), meaning
that SpamAssassin should close any per-user databases it has open, and re-open that SpamAssassin should close any per-user databases it has open, and re-open
@ -853,13 +853,13 @@ sub signal_user_changed {
userstate_dir => $self->{userstate_dir}, userstate_dir => $self->{userstate_dir},
user_dir => $self->{user_dir}, user_dir => $self->{user_dir},
}); });
undef $self->{cf_files_read};
1; 1;
} }
########################################################################### ###########################################################################
=item $f->report_as_spam ($mail, $options) =item $f-E<gt>report_as_spam ($mail, $options)
Report a mail, encapsulated in a C<Mail::SpamAssassin::Message> object, as Report a mail, encapsulated in a C<Mail::SpamAssassin::Message> object, as
human-verified spam. This will submit the mail message to live, human-verified spam. This will submit the mail message to live,
@ -912,7 +912,7 @@ sub report_as_spam {
########################################################################### ###########################################################################
=item $f->revoke_as_spam ($mail, $options) =item $f-E<gt>revoke_as_spam ($mail, $options)
Revoke a mail, encapsulated in a C<Mail::SpamAssassin::Message> object, as Revoke a mail, encapsulated in a C<Mail::SpamAssassin::Message> object, as
human-verified ham (non-spam). This will revoke the mail message from live, human-verified ham (non-spam). This will revoke the mail message from live,
@ -952,7 +952,7 @@ sub revoke_as_spam {
########################################################################### ###########################################################################
=item $f->add_address_to_welcomelist ($addr, $cli_p) =item $f-E<gt>add_address_to_welcomelist ($addr, $cli_p)
Previously add_address_to_whitelist which will work interchangeably until 4.1. Previously add_address_to_whitelist which will work interchangeably until 4.1.
@ -973,7 +973,7 @@ sub add_address_to_welcomelist {
########################################################################### ###########################################################################
=item $f->add_all_addresses_to_welcomelist ($mail, $cli_p) =item $f-E<gt>add_all_addresses_to_welcomelist ($mail, $cli_p)
Previously add_all_addresses_to_whitelist which will work interchangeably until 4.1. Previously add_all_addresses_to_whitelist which will work interchangeably until 4.1.
@ -996,7 +996,7 @@ sub add_all_addresses_to_welcomelist {
########################################################################### ###########################################################################
=item $f->remove_address_from_welcomelist ($addr, $cli_p) =item $f-E<gt>remove_address_from_welcomelist ($addr, $cli_p)
Previously remove_address_from_whitelist which will work interchangeably until 4.1. Previously remove_address_from_whitelist which will work interchangeably until 4.1.
@ -1017,7 +1017,7 @@ sub remove_address_from_welcomelist {
########################################################################### ###########################################################################
=item $f->remove_all_addresses_from_welcomelist ($mail, $cli_p) =item $f-E<gt>remove_all_addresses_from_welcomelist ($mail, $cli_p)
Previously remove_all_addresses_from_whitelist which will work interchangeably until 4.1. Previously remove_all_addresses_from_whitelist which will work interchangeably until 4.1.
@ -1041,7 +1041,7 @@ sub remove_all_addresses_from_welcomelist {
########################################################################### ###########################################################################
=item $f->add_address_to_blocklist ($addr, $cli_p) =item $f-E<gt>add_address_to_blocklist ($addr, $cli_p)
Previously add_address_to_blacklist which will work interchangeably until 4.1. Previously add_address_to_blacklist which will work interchangeably until 4.1.
@ -1061,7 +1061,7 @@ sub add_address_to_blocklist {
########################################################################### ###########################################################################
=item $f->add_all_addresses_to_blocklist ($mail, $cli_p) =item $f-E<gt>add_all_addresses_to_blocklist ($mail, $cli_p)
Previously add_all_addresses_to_blacklist which will work interchangeably until 4.1. Previously add_all_addresses_to_blacklist which will work interchangeably until 4.1.
@ -1097,7 +1097,7 @@ sub add_all_addresses_to_blocklist {
########################################################################### ###########################################################################
=item $text = $f->remove_spamassassin_markup ($mail) =item $text = $f-E<gt>remove_spamassassin_markup ($mail)
Returns the text of the message, with any SpamAssassin-added text (such Returns the text of the message, with any SpamAssassin-added text (such
as the report, or X-Spam-Status headers) stripped. as the report, or X-Spam-Status headers) stripped.
@ -1249,7 +1249,7 @@ sub remove_spamassassin_markup {
########################################################################### ###########################################################################
=item $f->read_scoreonly_config ($filename) =item $f-E<gt>read_scoreonly_config ($filename)
Read a configuration file and parse user preferences from it. Read a configuration file and parse user preferences from it.
@ -1292,7 +1292,7 @@ sub read_scoreonly_config {
########################################################################### ###########################################################################
=item $f->load_scoreonly_sql ($username) =item $f-E<gt>load_scoreonly_sql ($username)
Read configuration parameters from SQL database and parse scores from it. This Read configuration parameters from SQL database and parse scores from it. This
will only take effect if the perl C<DBI> module is installed, and the will only take effect if the perl C<DBI> module is installed, and the
@ -1318,7 +1318,7 @@ sub load_scoreonly_sql {
########################################################################### ###########################################################################
=item $f->load_scoreonly_ldap ($username) =item $f-E<gt>load_scoreonly_ldap ($username)
Read configuration parameters from an LDAP server and parse scores from it. Read configuration parameters from an LDAP server and parse scores from it.
This will only take effect if the perl C<Net::LDAP> and C<URI> modules are This will only take effect if the perl C<Net::LDAP> and C<URI> modules are
@ -1343,7 +1343,7 @@ sub load_scoreonly_ldap {
########################################################################### ###########################################################################
=item $f->set_persistent_address_list_factory ($factoryobj) =item $f-E<gt>set_persistent_address_list_factory ($factoryobj)
Set the persistent address list factory, used to create objects for the Set the persistent address list factory, used to create objects for the
automatic welcomelist algorithm's persistent-storage back-end. See automatic welcomelist algorithm's persistent-storage back-end. See
@ -1359,7 +1359,7 @@ sub set_persistent_address_list_factory {
########################################################################### ###########################################################################
=item $f->compile_now ($use_user_prefs, $keep_userstate) =item $f-E<gt>compile_now ($use_user_prefs, $keep_userstate)
Compile all patterns, load all configuration files, and load all Compile all patterns, load all configuration files, and load all
possibly-required Perl modules. possibly-required Perl modules.
@ -1471,7 +1471,7 @@ sub compile_now {
########################################################################### ###########################################################################
=item $f->debug_diagnostics () =item $f-E<gt>debug_diagnostics ()
Output some diagnostic information, useful for debugging SpamAssassin Output some diagnostic information, useful for debugging SpamAssassin
problems. problems.
@ -1491,7 +1491,7 @@ sub debug_diagnostics {
########################################################################### ###########################################################################
=item $failed = $f->lint_rules () =item $failed = $f-E<gt>lint_rules ()
Syntax-check the current set of rules. Returns the number of Syntax-check the current set of rules. Returns the number of
syntax errors discovered, or 0 if the configuration is valid. syntax errors discovered, or 0 if the configuration is valid.
@ -1536,7 +1536,7 @@ sub lint_rules {
########################################################################### ###########################################################################
=item $f->finish() =item $f-E<gt>finish()
Destroy this object, so that it will be garbage-collected once it Destroy this object, so that it will be garbage-collected once it
goes out of scope. The object will no longer be usable after this goes out of scope. The object will no longer be usable after this
@ -2017,7 +2017,7 @@ sub test_global_state_dir {
return 0; return 0;
} }
=item $fullpath = $f->find_rule_support_file ($filename) =item $fullpath = $f-E<gt>find_rule_support_file ($filename)
Find a rule-support file, such as C<languages> or C<triplets.txt>, Find a rule-support file, such as C<languages> or C<triplets.txt>,
in the system-wide rules directory, and return its full path if in the system-wide rules directory, and return its full path if
@ -2048,7 +2048,7 @@ sub find_rule_support_file {
map { my $p = $_; $p =~ s{$}{/$filename}; $p } @paths ); map { my $p = $_; $p =~ s{$}{/$filename}; $p } @paths );
} }
=item $f->create_default_prefs ($filename, $username [ , $userdir ] ) =item $f-E<gt>create_default_prefs ($filename, $username [ , $userdir ] )
Copy default preferences file into home directory for later use and Copy default preferences file into home directory for later use and
modification, if it does not already exist and C<dont_copy_prefs> is modification, if it does not already exist and C<dont_copy_prefs> is
@ -2374,7 +2374,7 @@ sub sa_die {
########################################################################### ###########################################################################
=item $f->copy_config ( [ $source ], [ $dest ] ) =item $f-E<gt>copy_config ( [ $source ], [ $dest ] )
Used for daemons to keep a persistent Mail::SpamAssassin object's Used for daemons to keep a persistent Mail::SpamAssassin object's
configuration correct if switching between users. Pass an associative configuration correct if switching between users. Pass an associative
@ -2425,7 +2425,7 @@ sub copy_config {
########################################################################### ###########################################################################
=item @plugins = $f->get_loaded_plugins_list ( ) =item @plugins = $f-E<gt>get_loaded_plugins_list ( )
Return the list of plugins currently loaded by this SpamAssassin object's Return the list of plugins currently loaded by this SpamAssassin object's
configuration; each entry in the list is an object of type configuration; each entry in the list is an object of type

View File

@ -77,7 +77,7 @@ and C<result_sub> functions appropriately per message.
########################################################################### ###########################################################################
=item $item = Mail::SpamAssassin::ArchiveIterator->new( [ { opt => val, ... } ] ) =item $item = Mail::SpamAssassin::ArchiveIterator-E<gt>new( [ { opt =E<gt> val, ... } ] )
Constructs a new C<Mail::SpamAssassin::ArchiveIterator> object. You may Constructs a new C<Mail::SpamAssassin::ArchiveIterator> object. You may
pass the following attribute-value pairs to the constructor. The pairs are pass the following attribute-value pairs to the constructor. The pairs are

View File

@ -85,7 +85,7 @@ sub new {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
=item $ent = $async->bgsend_and_start_lookup($name, $type, $class, $ent, $cb, %options) =item $ent = $async-E<gt>bgsend_and_start_lookup($name, $type, $class, $ent, $cb, %options)
Launch async DNS lookups. This is the only official method supported for Launch async DNS lookups. This is the only official method supported for
plugins since version 4.0.0. Do not use bgsend and start_lookup separately. plugins since version 4.0.0. Do not use bgsend and start_lookup separately.
@ -111,26 +111,26 @@ Deprecated, ignored, set as undef.
=over 4 =over 4
=item $ent->{rulename} (required) =item $ent-E<gt>{rulename} (required)
The rulename that started and/or depends on this query. Required for rule The rulename that started and/or depends on this query. Required for rule
dependencies to work correctly. Can be a single rulename, or array of dependencies to work correctly. Can be a single rulename, or array of
multiple rulenames. multiple rulenames.
=item $ent->{type} (optional) =item $ent-E<gt>{type} (optional)
A string, typically one word, used to describe the type of lookup in log A string, typically one word, used to describe the type of lookup in log
messages, such as C<DNSBL>, C<URIBL-A>. If not defined, default is value of messages, such as C<DNSBL>, C<URIBL-A>. If not defined, default is value of
$type. $type.
=item $ent->{zone} (optional) =item $ent-E<gt>{zone} (optional)
A zone specification (typically a DNS zone name - e.g. host, domain, or A zone specification (typically a DNS zone name - e.g. host, domain, or
RBL) which may be used as a key to look up per-zone settings. No semantics RBL) which may be used as a key to look up per-zone settings. No semantics
on this parameter is imposed by this module. Currently used to fetch on this parameter is imposed by this module. Currently used to fetch
by-zone timeouts (from rbl_timeout setting). Defaults to $name. by-zone timeouts (from rbl_timeout setting). Defaults to $name.
=item $ent->{timeout_initial} (optional) =item $ent-E<gt>{timeout_initial} (optional)
An initial value of elapsed time for which we are willing to wait for a An initial value of elapsed time for which we are willing to wait for a
response (time in seconds, floating point value is allowed). When elapsed response (time in seconds, floating point value is allowed). When elapsed
@ -147,17 +147,17 @@ variable rbl_timeout.
If a value of the timeout_initial parameter is below timeout_min, the initial If a value of the timeout_initial parameter is below timeout_min, the initial
timeout is set to timeout_min. timeout is set to timeout_min.
=item $ent->{timeout_min} (optional) =item $ent-E<gt>{timeout_min} (optional)
A lower bound (in seconds) to which the actual timeout approaches as the A lower bound (in seconds) to which the actual timeout approaches as the
number of queries completed approaches the number of all queries started. number of queries completed approaches the number of all queries started.
Defaults to 0.2 * timeout_initial. Defaults to 0.2 * timeout_initial.
=item $ent->{key}, $ent->{id} (deprecated) =item $ent-E<gt>{key}, $ent-E<gt>{id} (deprecated)
Deprecated, ignored, automatically generated since 4.0.0. Deprecated, ignored, automatically generated since 4.0.0.
=item $ent->{YOUR_OWN_ITEM} =item $ent-E<gt>{YOUR_OWN_ITEM}
Any other custom values/objects that you want to pass on to the answer Any other custom values/objects that you want to pass on to the answer
callback. callback.
@ -166,10 +166,10 @@ callback.
=item $cb (required) =item $cb (required)
Callback function for answer, called as $cb->($ent, $pkt). C<$ent> is the Callback function for answer, called as $cb-E<gt>($ent, $pkt). C<$ent> is the
same object that bgsend_and_start_lookup was called with. C<$pkt> is the same object that bgsend_and_start_lookup was called with. C<$pkt> is the
packet object for the response, Net::DNS:RR objects can be found from packet object for the response, Net::DNS:RR objects can be found from
$pkt->answer. $pkt-E<gt>answer.
=item %options (required) =item %options (required)
@ -386,7 +386,7 @@ sub bgsend_and_start_lookup {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
=item $ent = $async->start_lookup($ent, $master_deadline) =item $ent = $async-E<gt>start_lookup($ent, $master_deadline)
DIRECT USE DEPRECATED since 4.0.0, please use bgsend_and_start_lookup. DIRECT USE DEPRECATED since 4.0.0, please use bgsend_and_start_lookup.
@ -483,7 +483,7 @@ sub _start_lookup {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
=item $ent = $async->get_lookup($key) =item $ent = $async-E<gt>get_lookup($key)
DEPRECATED since 4.0.0. Do not use. DEPRECATED since 4.0.0. Do not use.
@ -497,7 +497,7 @@ sub get_lookup {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
=item $async->log_lookups_timing() =item $async-E<gt>log_lookups_timing()
Log sorted timing for all completed lookups. Log sorted timing for all completed lookups.
@ -513,7 +513,7 @@ sub log_lookups_timing {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
=item $alldone = $async->complete_lookups() =item $alldone = $async-E<gt>complete_lookups()
Perform a poll of the pending lookups, to see if any are completed. Perform a poll of the pending lookups, to see if any are completed.
Callbacks on completed queries will be called from poll_responses(). Callbacks on completed queries will be called from poll_responses().
@ -644,7 +644,7 @@ sub complete_lookups {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
=item $async->abort_remaining_lookups() =item $async-E<gt>abort_remaining_lookups()
Abort any remaining lookups. Abort any remaining lookups.
@ -712,21 +712,21 @@ sub abort_remaining_lookups {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
=item $async->set_response_packet($id, $pkt, $key, $timestamp) =item $async-E<gt>set_response_packet($id, $pkt, $key, $timestamp)
For internal use, do not call from plugins. For internal use, do not call from plugins.
Register a "response packet" for a given query. C<$id> is the ID for the Register a "response packet" for a given query. C<$id> is the ID for the
query, and must match the C<id> supplied in C<start_lookup()>. C<$pkt> is the query, and must match the C<id> supplied in C<start_lookup()>. C<$pkt> is the
packet object for the response. A parameter C<$key> identifies an entry in a packet object for the response. A parameter C<$key> identifies an entry in a
hash %{$self->{pending_lookups}} where the object which spawned this query can hash %{$self-E<gt>{pending_lookups}} where the object which spawned this query can
be found, and through which further information about the query is accessible. be found, and through which further information about the query is accessible.
C<$pkt> may be undef, indicating that no response packet is available, but a C<$pkt> may be undef, indicating that no response packet is available, but a
query has completed (e.g. was aborted or dismissed) and is no longer "pending". query has completed (e.g. was aborted or dismissed) and is no longer "pending".
The DNS resolver's response packet C<$pkt> will be made available to a callback The DNS resolver's response packet C<$pkt> will be made available to a callback
subroutine through its argument as well as in C<$ent-<gt>{response_packet}>. subroutine through its argument as well as in C<$ent-E<gt>{response_packet}>.
=cut =cut
@ -765,11 +765,11 @@ sub set_response_packet {
1; 1;
} }
=item $async->report_id_complete($id,$key,$key,$timestamp) =item $async-E<gt>report_id_complete($id,$key,$key,$timestamp)
DEPRECATED since 4.0.0. Do not use. DEPRECATED since 4.0.0. Do not use.
Legacy. Equivalent to $self->set_response_packet($id,undef,$key,$timestamp), Legacy. Equivalent to $self-E<gt>set_response_packet($id,undef,$key,$timestamp),
i.e. providing undef as a response packet. Register that a query has i.e. providing undef as a response packet. Register that a query has
completed and is no longer "pending". C<$id> is the ID for the query, completed and is no longer "pending". C<$id> is the ID for the query,
and must match the C<id> supplied in C<start_lookup()>. and must match the C<id> supplied in C<start_lookup()>.
@ -786,7 +786,7 @@ sub report_id_complete {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
=item $time = $async->last_poll_responses_time() =item $time = $async-E<gt>last_poll_responses_time()
Get the time of the last call to C<poll_responses()> (which is called Get the time of the last call to C<poll_responses()> (which is called
from C<complete_lookups()>. If C<poll_responses()> was never called or from C<complete_lookups()>. If C<poll_responses()> was never called or

View File

@ -105,7 +105,7 @@ sub new {
########################################################################### ###########################################################################
=item $meanscore = awl->check_address($addr, $originating_ip, $signedby); =item $meanscore = awl-E<gt>check_address($addr, $originating_ip, $signedby);
This method will return the mean score of all messages associated with the This method will return the mean score of all messages associated with the
given address, or undef if the address hasn't been seen before. given address, or undef if the address hasn't been seen before.
@ -160,7 +160,7 @@ sub check_address {
########################################################################### ###########################################################################
=item awl->count(); =item awl-E<gt>count();
This method will return the count of messages used in determining the This method will return the count of messages used in determining the
welcomelist correction. welcomelist correction.
@ -175,7 +175,7 @@ sub count {
########################################################################### ###########################################################################
=item awl->add_score($score); =item awl-E<gt>add_score($score);
This method will add half the score to the current entry. Half the This method will add half the score to the current entry. Half the
score is used, so that repeated use of the same From and IP address score is used, so that repeated use of the same From and IP address
@ -200,7 +200,7 @@ sub add_score {
########################################################################### ###########################################################################
=item awl->add_known_good_address($addr); =item awl-E<gt>add_known_good_address($addr);
This method will add a score of -100 to the given address -- effectively This method will add a score of -100 to the given address -- effectively
"bootstrapping" the address as being one that should be welcomelisted. "bootstrapping" the address as being one that should be welcomelisted.
@ -216,7 +216,7 @@ sub add_known_good_address {
########################################################################### ###########################################################################
=item awl->add_known_bad_address($addr); =item awl-E<gt>add_known_bad_address($addr);
This method will add a score of 100 to the given address -- effectively This method will add a score of 100 to the given address -- effectively
"bootstrapping" the address as being one that should be blocklisted. "bootstrapping" the address as being one that should be blocklisted.

View File

@ -510,7 +510,7 @@ sub _create_connection {
} }
unless ($remote) { unless ($remote) {
print "Failed to create connection to spamd daemon: $!\n"; warn "Failed to create connection to spamd daemon: $!\n";
return; return;
} }
@ -594,7 +594,7 @@ sub _filter {
if(defined $self->{max_size}) { if(defined $self->{max_size}) {
$msg = substr($msg,0,$self->{max_size}); $msg = substr($msg,0,$self->{max_size});
} }
$msgsize = length($msg.$EOL); $msgsize = length($msg);
print $remote "$command $PROTOVERSION$EOL"; print $remote "$command $PROTOVERSION$EOL";
print $remote "Content-length: $msgsize$EOL"; print $remote "Content-length: $msgsize$EOL";

View File

@ -1544,7 +1544,7 @@ Empty the list of msa networks.
} }
}); });
=item originating_ip_headers header ... (default: X-Yahoo-Post-IP X-Originating-IP X-Apparently-From X-SenderIP) =item originating_ip_headers header ... (default: none)
A list of header field names from which an originating IP address can A list of header field names from which an originating IP address can
be obtained. For example, webmail servers may record a client IP address be obtained. For example, webmail servers may record a client IP address
@ -1556,6 +1556,10 @@ are used in RBL checks where appropriate.
Currently the IP addresses are not added into X-Spam-Relays-* header fields, Currently the IP addresses are not added into X-Spam-Relays-* header fields,
but they may be in the future. but they may be in the future.
A default list may be supplied via sa-update, use
C<clear_originating_ip_headers> to clear and override the settings if
needed.
=cut =cut
push (@cmds, { push (@cmds, {
@ -1577,7 +1581,8 @@ but they may be in the future.
=item clear_originating_ip_headers =item clear_originating_ip_headers
Empty the list of 'originating IP address' header field names. Empty the list of 'originating IP address' header field names. Useful if
you want to override the standard list supplied by sa-update.
=cut =cut
@ -2077,7 +2082,7 @@ home_dir_for_helpers/.spamassassin, $HOME/.spamassassin,
my $rule = $1; my $rule = $1;
foreach my $domain (split(/\s+/, lc($2))) { foreach my $domain (split(/\s+/, lc($2))) {
$domain =~ s/^\.//; $domain =~ s/\.\z//; # strip dots $domain =~ s/^\.//; $domain =~ s/\.\z//; # strip dots
if ($domain !~ /^[a-z0-9.-]+$/) { if ($domain !~ /^[a-z0-9_.-]+$/) {
return $INVALID_VALUE; return $INVALID_VALUE;
} }
# will end up in filename, do not allow / etc in above regex! # will end up in filename, do not allow / etc in above regex!
@ -3677,10 +3682,6 @@ normal rules to use. When rule depends on a tag that might be set at later
stage by a plugin for example, it's priority should be set manually to a stage by a plugin for example, it's priority should be set manually to a
higher value. higher value.
=over 4
=back
=head1 ADMINISTRATOR SETTINGS =head1 ADMINISTRATOR SETTINGS
These settings differ from the ones above, in that they are considered 'more These settings differ from the ones above, in that they are considered 'more

View File

@ -79,7 +79,7 @@ sub load_modules { # static
########################################################################### ###########################################################################
=item $f->load ($username) =item $f-E<gt>load ($username)
Read configuration parameters from LDAP server and parse scores from it. Read configuration parameters from LDAP server and parse scores from it.

View File

@ -77,7 +77,7 @@ sub load_modules { # static
########################################################################### ###########################################################################
=item $f->load ($username) =item $f-E<gt>load ($username)
Read configuration parameters from SQL database and parse scores from it. Read configuration parameters from SQL database and parse scores from it.

View File

@ -65,29 +65,6 @@ our $IS_DNS_AVAILABLE = undef;
########################################################################### ###########################################################################
BEGIN {
# some trickery. Load these modules right here, if possible; that way, if
# the module exists, we'll get it loaded now. Very useful to avoid attempted
# loads later (which will happen). If we do a fork(), we could wind up
# attempting to load these modules in *every* subprocess.
#
# # We turn off strict and warnings, because Net::DNS and Razor both contain
# # crud that -w complains about (perl 5.6.0). Not that this seems to work,
# # mind ;)
# no strict;
# local ($^W) = 0;
no warnings;
eval {
require MIME::Base64;
};
eval {
require IO::Socket::UNIX;
};
};
###########################################################################
sub do_rbl_lookup { sub do_rbl_lookup {
my ($self, $rule, $set, $type, $host, $subtest) = @_; my ($self, $rule, $set, $type, $host, $subtest) = @_;

View File

@ -86,7 +86,7 @@ sub new {
########################################################################### ###########################################################################
=item $res->load_resolver() =item $res-E<gt>load_resolver()
Load the C<Net::DNS::Resolver> object. Returns 0 if Net::DNS cannot be used, Load the C<Net::DNS::Resolver> object. Returns 0 if Net::DNS cannot be used,
1 if it is available. 1 if it is available.
@ -185,7 +185,7 @@ sub load_resolver {
return defined $self->{res}; return defined $self->{res};
} }
=item $resolver = $res->get_resolver() =item $resolver = $res-E<gt>get_resolver()
Return the C<Net::DNS::Resolver> object. Return the C<Net::DNS::Resolver> object.
@ -196,7 +196,7 @@ sub get_resolver {
return $self->{res}; return $self->{res};
} }
=item $res->configured_nameservers() =item $res-E<gt>configured_nameservers()
Get a list of nameservers as configured by dns_server directives Get a list of nameservers as configured by dns_server directives
or as provided by Net::DNS, typically from /etc/resolv.conf or as provided by Net::DNS, typically from /etc/resolv.conf
@ -221,7 +221,7 @@ sub configured_nameservers {
return @ns_addr_port; return @ns_addr_port;
} }
=item $res->available_nameservers() =item $res-E<gt>available_nameservers()
Get or set a list of currently available nameservers, Get or set a list of currently available nameservers,
which is typically a known-to-be-good subset of configured nameservers which is typically a known-to-be-good subset of configured nameservers
@ -346,7 +346,7 @@ sub pick_random_available_port {
return $port_number; return $port_number;
} }
=item $res->connect_sock() =item $res-E<gt>connect_sock()
Re-connect to the first nameserver listed in C</etc/resolv.conf> or similar Re-connect to the first nameserver listed in C</etc/resolv.conf> or similar
platform-dependent source, as provided by C<Net::DNS>. platform-dependent source, as provided by C<Net::DNS>.
@ -477,7 +477,7 @@ sub connect_sock_if_reqd {
$self->connect_sock() if !$self->{sock}; $self->connect_sock() if !$self->{sock};
} }
=item $res->get_sock() =item $res-E<gt>get_sock()
Return the C<IO::Socket::INET> object used to communicate with Return the C<IO::Socket::INET> object used to communicate with
the nameserver. the nameserver.
@ -599,7 +599,11 @@ sub new_dns_packet {
my $udp_payload_size = $self->{conf}->{dns_options}->{edns}; my $udp_payload_size = $self->{conf}->{dns_options}->{edns};
if ($udp_payload_size && $udp_payload_size > 512) { if ($udp_payload_size && $udp_payload_size > 512) {
# dbg("dns: adding EDNS ext, UDP payload size %d", $udp_payload_size); # dbg("dns: adding EDNS ext, UDP payload size %d", $udp_payload_size);
$packet->edns->size($udp_payload_size); if ($packet->edns->can('udpsize')) { # since Net::DNS 1.38
$packet->edns->udpsize($udp_payload_size);
} else {
$packet->edns->size($udp_payload_size);
}
} }
} }
@ -651,7 +655,7 @@ sub _packet_id {
########################################################################### ###########################################################################
=item $id = $res->bgsend($domain, $type, $class, $cb) =item $id = $res-E<gt>bgsend($domain, $type, $class, $cb)
DIRECT USE DISCOURAGED, please use bgsend_and_start_lookup in plugins. DIRECT USE DISCOURAGED, please use bgsend_and_start_lookup in plugins.
@ -734,7 +738,7 @@ sub bgsend {
########################################################################### ###########################################################################
=item $id = $res->bgread() =item $id = $res-E<gt>bgread()
Similar to C<Net::DNS::Resolver::bgread>. Reads a DNS packet from Similar to C<Net::DNS::Resolver::bgread>. Reads a DNS packet from
a supplied socket, decodes it, and returns a Net::DNS::Packet object a supplied socket, decodes it, and returns a Net::DNS::Packet object
@ -765,7 +769,7 @@ sub bgread {
########################################################################### ###########################################################################
=item $nfound = $res->poll_responses() =item $nfound = $res-E<gt>poll_responses()
See if there are any C<bgsend> reply packets ready, and return See if there are any C<bgsend> reply packets ready, and return
the number of such packets delivered to their callbacks. the number of such packets delivered to their callbacks.
@ -833,6 +837,30 @@ sub poll_responses {
info("dns: bad dns reply: %s", $eval_stat); info("dns: bad dns reply: %s", $eval_stat);
}; };
# bug 8225 - Do TCP fallback when UDP reply packet is too long, by retrying using Net::DNS::Resolver bgsend and bgread
my ($id, $packet_id);
if ($packet && $packet->header) {
my $header = $packet->header;
$packet_id = $header->id; # set these here in case we need to retry for TCP fallback
$id = $self->_packet_id($packet); # which will change $packet to a different class object
if ($header->rcode eq 'NOERROR' && $header->tc) {
# Use original Resolver which can handle TCP fallback, but keep id from the custom packet
my (undef, $qclass, $qtype, $qname) = split('/', $id);
dbg("dns: TCP fallback retry with %s, %s, %s", $qname, $qtype, $qclass);
my $orig_resolver = $self->{main}->{resolver}->get_resolver();
eval {
my $handle = $orig_resolver->bgsend($qname, $qtype, $qclass);
$packet = $orig_resolver->bgread($handle);
} or do {
undef $packet;
my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
# resignal if alarm went off
die $eval_stat if $eval_stat =~ /__alarm__ignore__\(.*\)/s;
info("dns: bad dns tcp fallback reply: %s", $eval_stat);
};
}
}
if (!$packet) { if (!$packet) {
# error already reported above # error already reported above
# my $dns_err = $self->{res}->errorstring; # my $dns_err = $self->{res}->errorstring;
@ -844,9 +872,6 @@ sub poll_responses {
info("dns: dns reply is missing a header section"); info("dns: dns reply is missing a header section");
} else { } else {
my $rcode = $header->rcode; my $rcode = $header->rcode;
my $packet_id = $header->id;
my $id = $self->_packet_id($packet);
if ($rcode eq 'NOERROR') { # success if ($rcode eq 'NOERROR') { # success
# NOERROR, may or may not have answer records # NOERROR, may or may not have answer records
dbg("dns: dns reply %s is OK, %d answer records", dbg("dns: dns reply %s is OK, %d answer records",
@ -930,7 +955,7 @@ sub flush_responses {
########################################################################### ###########################################################################
=item $res->bgabort() =item $res-E<gt>bgabort()
Call this to release pending requests from memory, when aborting backgrounded Call this to release pending requests from memory, when aborting backgrounded
requests, or when the scan is complete. requests, or when the scan is complete.
@ -945,7 +970,7 @@ sub bgabort {
########################################################################### ###########################################################################
=item $packet = $res->send($name, $type, $class) =item $packet = $res-E<gt>send($name, $type, $class)
Emulates C<Net::DNS::Resolver::send()>. Emulates C<Net::DNS::Resolver::send()>.
@ -1002,12 +1027,12 @@ sub send {
########################################################################### ###########################################################################
=item $res->errorstring() =item $res-E<gt>errorstring()
Little more than a stub for callers expecting this from C<Net::DNS::Resolver>. Little more than a stub for callers expecting this from C<Net::DNS::Resolver>.
If called immediately after a call to $res->send this will return If called immediately after a call to $res-E<gt>send this will return
C<query timed out> if the $res->send DNS query timed out. Otherwise C<query timed out> if the $res-E<gt>send DNS query timed out. Otherwise
C<unknown error or no error> will be returned. C<unknown error or no error> will be returned.
No other errors are reported. No other errors are reported.
@ -1022,7 +1047,7 @@ sub errorstring {
########################################################################### ###########################################################################
=item $res->finish_socket() =item $res-E<gt>finish_socket()
Reset socket when done with it. Reset socket when done with it.
@ -1039,7 +1064,7 @@ sub finish_socket {
########################################################################### ###########################################################################
=item $res->finish() =item $res-E<gt>finish()
Clean up for destruction. Clean up for destruction.

View File

@ -24,8 +24,8 @@ Plugins need to signal SA main package the modules they want loaded
package Mail::SpamAssassin::Plugin::MyPlugin; package Mail::SpamAssassin::Plugin::MyPlugin;
sub new { sub new {
... ...
$self->{main}->{geodb_wanted}->{country} = 1; $self-E<gt>{main}-E<gt>{geodb_wanted}-E<gt>{country} = 1;
$self->{main}->{geodb_wanted}->{isp} = 1; $self-E<gt>{main}-E<gt>{geodb_wanted}-E<gt>{isp} = 1;
) )
(internal stuff still subject to change) (internal stuff still subject to change)

View File

@ -349,12 +349,17 @@ sub push_uri {
my ($self, $type, $uri) = @_; my ($self, $type, $uri) = @_;
$uri = $self->canon_uri($uri); $uri = $self->canon_uri($uri);
return if $uri eq '';
utf8::encode($uri) if $self->{SA_encode_results}; utf8::encode($uri) if $self->{SA_encode_results};
my $target = target_uri($self->{base_href} || "", $uri); if ($uri =~ /^(?:data|mailto|file|cid|tel):/i) {
# No target handling required
# skip things like <iframe src="" ...> $self->{uri}->{$uri}->{types}->{$type} = 1;
$self->{uri}->{$uri}->{types}->{$type} = 1 if $uri ne ''; } else {
my $target = target_uri($self->{base_href} || "", $uri);
# skip things like <iframe src="" ...>
$self->{uri}->{$target}->{types}->{$type} = 1 if $target ne '';
}
} }
sub canon_uri { sub canon_uri {
@ -383,7 +388,20 @@ sub html_uri {
} }
} }
elsif ($tag =~ /^(?:a|area|link)$/) { elsif ($tag =~ /^(?:a|area|link)$/) {
while ( my ( $k, $v ) = each %$attr ) {
# read uris from bad formatted html as well
if($k =~ /\w{1,8}\/href/) {
delete($attr->{$k});
$attr->{href} = $v;
}
}
if (defined $attr->{href}) { if (defined $attr->{href}) {
# Remove the Unicode "replacement character" from the url
if (utf8::is_utf8($attr->{href})) {
$attr->{href} =~ s/\x{FEFF}//g;
} else {
$attr->{href} =~ s/\x{EF}\x{BB}\x{BF}//g;
}
$self->push_uri($tag, $attr->{href}); $self->push_uri($tag, $attr->{href});
} }
if (defined $attr->{'data-saferedirecturl'}) { if (defined $attr->{'data-saferedirecturl'}) {
@ -391,6 +409,13 @@ sub html_uri {
} }
} }
elsif ($tag =~ /^(?:img|frame|iframe|embed|script|bgsound)$/) { elsif ($tag =~ /^(?:img|frame|iframe|embed|script|bgsound)$/) {
while ( my ( $k, $v ) = each %$attr ) {
# read uris from bad formatted html as well
if($k =~ /\w{1,8}\/src/) {
delete($attr->{$k});
$attr->{src} = $v;
}
}
if (defined $attr->{src}) { if (defined $attr->{src}) {
$self->push_uri($tag, $attr->{src}); $self->push_uri($tag, $attr->{src});
} }
@ -535,6 +560,9 @@ sub text_style {
if (/^\s*(background-)?color:\s*(.+?)\s*$/i) { if (/^\s*(background-)?color:\s*(.+?)\s*$/i) {
my $whcolor = $1 ? 'bgcolor' : 'fgcolor'; my $whcolor = $1 ? 'bgcolor' : 'fgcolor';
my $value = lc $2; my $value = lc $2;
# prevent parsing of the valid CSS3 property value as
# 'invalid color' (Bug 7892)
$value =~ s/\s*!\s*important$//;
if (index($value, 'rgb') >= 0) { if (index($value, 'rgb') >= 0) {
$value =~ tr/0-9,//cd; $value =~ tr/0-9,//cd;
@ -547,19 +575,41 @@ sub text_style {
# do nothing, just prevent parsing of the valid # do nothing, just prevent parsing of the valid
# CSS3 property value as 'invalid color' (Bug 7778) # CSS3 property value as 'invalid color' (Bug 7778)
} }
elsif ($value eq '!important') { elsif ($value eq 'transparent') {
# do nothing, just prevent parsing of the valid # keep for now, handle outside the loop (Bug 8205)
# CSS3 property value as 'invalid color' (Bug 7892) $new{$whcolor} = $value;
} }
else { else {
$new{$whcolor} = name_to_rgb($value); $new{$whcolor} = name_to_rgb($value);
} }
} }
elsif (/^\s*background:\s*(.+?)\s*$/) {
# parse CSS background property (Bug 8210)
my $layers = parse_css_background(lc($1));
# loop through values in the bottom layer and look for valid colors
for my $value (@{$layers->[-1]}) {
my $color = name_to_rgb($value);
if ($color ne 'invalid') {
$new{bgcolor} = $color;
last;
}
}
}
elsif (/^\s*([a-z_-]+)\s*:\s*(\S.*?)\s*$/i) { elsif (/^\s*([a-z_-]+)\s*:\s*(\S.*?)\s*$/i) {
# "display: none", "visibility: hidden", etc. # "display: none", "visibility: hidden", etc.
$new{'style_'.$1} = $2; $new{'style_'.$1} = $2;
} }
} }
# Handle transparent colors (Bug 8205)
if ($new{bgcolor} eq 'transparent') {
# replace with parent's bgcolor
$new{bgcolor} = $self->{text_style}[-1]->{bgcolor};
}
if ($new{fgcolor} eq 'transparent') {
# replace with current bgcolor
$new{fgcolor} = $new{bgcolor};
}
} }
elsif ($name eq "bgcolor") { elsif ($name eq "bgcolor") {
# overwrite with hex value, $new{bgcolor} is set below # overwrite with hex value, $new{bgcolor} is set below
@ -588,6 +638,92 @@ sub text_style {
} }
} }
# Parses a CSS background property value.
# Returns an arrayref of layers, each layer is an arrayref of values such as
# [
# 'rgb(255, 192, 0)',
# '35%',
# 'url("../../media/examples/lizard.png")'
# ]
# https://developer.mozilla.org/en-US/docs/Web/CSS/background
sub parse_css_background {
my ($background) = @_;
my @layers;
my @tokens;
my @stack;
my ($state,$token) = (0,'');
for (my $i=0;$i < length($background);$i++) {
my $ch = substr($background, $i, 1);
if ($state == 0) {
if ($ch eq ' ') {
push @tokens, $token if $token ne '';
$token = '';
} elsif ($ch eq '(') {
$token .= $ch;
push @stack, $state;
$state = 1;
} elsif ($ch eq '"') {
$token .= $ch;
push @stack, $state;
$state = 2;
} elsif ($ch eq q(')) {
$token .= $ch;
push @stack, $state;
$state = 3;
} elsif ($ch eq ',') {
push @tokens, $token if $token ne '';
$token = '';
push @layers, [ @tokens ];
@tokens = ();
} else {
$token .= $ch;
}
} elsif ($state == 1) {
if ($ch eq ')') {
$token .= $ch;
push @tokens, $token;
$token = '';
$state = pop @stack;
} elsif ($ch eq '"') {
$token .= $ch;
push(@stack, $state);
$state = 2;
} elsif ($ch eq q(')) {
$token .= $ch;
push(@stack, $state);
$state = 3;
} else {
$token .= $ch;
}
} elsif ($state == 2) {
if ($ch eq '"') {
$token .= $ch;
$state = pop @stack;
} else {
$token .= $ch;
}
} elsif ($state == 3) {
if ($ch eq q(')) {
$token .= $ch;
$state = pop @stack;
} else {
$token .= $ch;
}
}
}
if ($token ne '') {
push @tokens, $token;
}
if ( scalar @tokens > 0 ) {
push @layers, [ @tokens ];
}
return \@layers;
}
sub html_font_invisible { sub html_font_invisible {
my ($self, $text) = @_; my ($self, $text) = @_;
@ -1255,7 +1391,7 @@ sub target_uri {
$result .= "ftp:"; $result .= "ftp:";
} }
} }
if ($t{authority}) { if (defined $t{authority}) {
$result .= "//" . $t{authority}; $result .= "//" . $t{authority};
} }
$result .= $t{path}; $result .= $t{path};

View File

@ -293,6 +293,7 @@ sub _log {
my ($level, $message, @args) = @_; my ($level, $message, @args) = @_;
utf8::encode($message) if utf8::is_utf8($message); # handle as octets utf8::encode($message) if utf8::is_utf8($message); # handle as octets
foreach (@args) { utf8::encode($_) if utf8::is_utf8($_); } # Bug 8138
$message =~ s/^(?:[a-z0-9_-]*):\s*//i; $message =~ s/^(?:[a-z0-9_-]*):\s*//i;
@ -304,7 +305,7 @@ sub _log {
log_message(($level == INFO ? "info" : "dbg"), $message); log_message(($level == INFO ? "info" : "dbg"), $message);
} }
=item add(method => 'syslog', socket => $socket, facility => $facility, escape => $escape) =item add(method =E<gt> 'syslog', socket =E<gt> $socket, facility =E<gt> $facility, escape =E<gt> $escape)
C<socket> is the type the syslog ("unix" or "inet"). C<facility> is the C<socket> is the type the syslog ("unix" or "inet"). C<facility> is the
syslog facility (typically "mail"). syslog facility (typically "mail").
@ -317,12 +318,12 @@ output: backslashes change to \\ and non-ascii chars to \x{XX} or \x{XXXX}
Escape value can be overridden with environment variable Escape value can be overridden with environment variable
C<SA_LOGGER_ESCAPE>. C<SA_LOGGER_ESCAPE>.
=item add(method => 'file', filename => $file, escape => $escape) =item add(method =E<gt> 'file', filename =E<gt> $file, escape =E<gt> $escape)
C<filename> is the name of the log file. C<escape> works as described C<filename> is the name of the log file. C<escape> works as described
above. above.
=item add(method => 'stderr', escape => $escape) =item add(method =E<gt> 'stderr', escape =E<gt> $escape)
No options are needed for stderr logging, just don't close stderr first. No options are needed for stderr logging, just don't close stderr first.
C<escape> works as described above. C<escape> works as described above.

View File

@ -572,8 +572,8 @@ sub get_pristine_body_digest {
=item get_msgid() =item get_msgid()
Returns Message-ID header for the message, with <> and surrounding Returns Message-ID header for the message, with E<lt>E<gt> and surrounding
whitespace removed. Returns undef, if nothing found between <>. whitespace removed. Returns undef, if nothing found between E<lt>E<gt>.
=cut =cut
@ -759,6 +759,7 @@ sub finish {
delete $part->{'invisible_rendered'}; delete $part->{'invisible_rendered'};
delete $part->{'type'}; delete $part->{'type'};
delete $part->{'rendered_type'}; delete $part->{'rendered_type'};
delete $part->{'effective_type'};
# if there are children nodes, add them to the queue of nodes to clean up # if there are children nodes, add them to the queue of nodes to clean up
if (exists $part->{'body_parts'}) { if (exists $part->{'body_parts'}) {
@ -1261,10 +1262,11 @@ sub get_body_text_array_common {
# already been done. # already been done.
my $html_needs_setting = !exists $self->{metadata}->{html}; my $html_needs_setting = !exists $self->{metadata}->{html};
my $text = $method_name eq 'invisible_rendered' ? '' my $subject = $method_name eq 'invisible_rendered' ? ''
: ($self->get_header('subject') || "\n"); : ($self->get_header('subject') || "\n");
# Go through each part # Go through each part
my $text = '';
for (my $pt = 0 ; $pt <= $#parts ; $pt++ ) { for (my $pt = 0 ; $pt <= $#parts ; $pt++ ) {
my $p = $parts[$pt]; my $p = $parts[$pt];
@ -1316,7 +1318,8 @@ sub get_body_text_array_common {
$text =~ tr/\x00/\n/; # null => newline $text =~ tr/\x00/\n/; # null => newline
utf8::encode($text) if utf8::is_utf8($text); utf8::encode($text) if utf8::is_utf8($text);
my @textary = split_into_array_of_short_lines($text); utf8::encode($subject) if utf8::is_utf8($subject);
my @textary = split_into_array_of_short_lines($subject.$text);
$self->{$key} = \@textary; $self->{$key} = \@textary;
return $self->{$key}; return $self->{$key};
@ -1350,11 +1353,13 @@ sub get_decoded_body_text_array {
my $scansize = $self->{rawbody_part_scan_size}; my $scansize = $self->{rawbody_part_scan_size};
# Find all parts which are leaves # Find all parts which are leaves
my @parts = $self->find_parts(qr/^(?:text|message)\b/,1); my @parts = $self->find_parts(qr/./,1);
return $self->{text_decoded} unless @parts; return $self->{text_decoded} unless @parts;
# Go through each part # Go through each part
for(my $pt = 0 ; $pt <= $#parts ; $pt++ ) { for(my $pt = 0 ; $pt <= $#parts ; $pt++ ) {
# skip non-text parts (Bug 6439)
next unless $parts[$pt]->effective_type() =~ /^(?:text|message)\b/;
# bug 4843: skip text/calendar parts since they're usually an attachment # bug 4843: skip text/calendar parts since they're usually an attachment
# and not displayed # and not displayed
next if ($parts[$pt]->{'type'} eq 'text/calendar'); next if ($parts[$pt]->{'type'} eq 'text/calendar');

View File

@ -28,12 +28,12 @@ supplemental data inferred from the message, like the examples below.
It is held in two forms: It is held in two forms:
1. as name-value pairs of strings, presented in mail header format. For 1. as name-value pairs of strings, presented in mail header format. For
example, "X-Languages" => "en". This is the general form for simple example, "X-Languages" =E<gt> "en". This is the general form for simple
metadata that's useful as Bayes tokens, can be added to marked-up metadata that's useful as Bayes tokens, can be added to marked-up
messages using "add_header", etc., such as the trusted-relay inference messages using "add_header", etc., such as the trusted-relay inference
and language detection. and language detection.
2. as more complex data structures on the $msg->{metadata} object. This 2. as more complex data structures on the $msg-E<gt>{metadata} object. This
is the form used for metadata like the HTML parse data, which is stored is the form used for metadata like the HTML parse data, which is stored
there for access by eval rule code. Because it's not simple strings, there for access by eval rule code. Because it's not simple strings,
it's not added as a Bayes token by default (Bayes needs simple strings). it's not added as a Bayes token by default (Bayes needs simple strings).

View File

@ -686,6 +686,19 @@ sub _normalize {
return $rv; return $rv;
} }
# Parse effective content type (Bug 6260, 6439)
sub effective_type {
my ($self) = @_;
if (!exists $self->{'effective_type'}) {
if (($self->{'name'}||'') =~ /\.s?html?$/i) {
$self->{'effective_type'} = 'text/html';
} else {
$self->{'effective_type'} = $self->{'type'};
}
}
return $self->{'effective_type'};
}
=item rendered() =item rendered()
rendered() takes the given text/* type MIME part, and attempts to rendered() takes the given text/* type MIME part, and attempts to
@ -708,7 +721,7 @@ sub rendered {
# Note: for bug 4843, make sure to skip text/calendar parts # Note: for bug 4843, make sure to skip text/calendar parts
# we also want to skip things like text/x-vcard # we also want to skip things like text/x-vcard
# text/x-aol is ignored here, but looks like text/html ... # text/x-aol is ignored here, but looks like text/html ...
my $type = lc $self->{'type'}; my $type = $self->effective_type();
unless ($type eq 'text/plain' || $type eq 'text/html') { unless ($type eq 'text/plain' || $type eq 'text/html') {
return (undef,undef); return (undef,undef);
} }

View File

@ -157,7 +157,7 @@ sub forget {
########################################################################### ###########################################################################
=item $didlearn = $status->did_learn() =item $didlearn = $status-E<gt>did_learn()
Returns C<1> if the message was learned from or forgotten successfully. Returns C<1> if the message was learned from or forgotten successfully.
@ -170,7 +170,7 @@ sub did_learn {
########################################################################### ###########################################################################
=item $status->finish() =item $status-E<gt>finish()
Finish with the object. Finish with the object.

View File

@ -332,8 +332,11 @@ sub new {
# in some circumstances # in some circumstances
my $tag_data_ref = $self->{tag_data}; my $tag_data_ref = $self->{tag_data};
foreach (qw(SUMMARY REPORT SUBJPREFIX RBL)) { $tag_data_ref->{$_} = '' } foreach (qw(SUMMARY REPORT SUBJPREFIX RBL)) { $tag_data_ref->{$_} = '' }
foreach (qw(AWL AWLMEAN AWLCOUNT AWLPRESCORE foreach (qw(ASN ASNCIDR AWL AWLMEAN AWLCOUNT AWLPRESCORE
DCCB DCCR DCCREP PYZOR DKIMIDENTITY DKIMDOMAIN DKIMSELECTOR DCCB DCCR EXTRACTTEXTCHARS EXTRACTTEXTWORDS
EXTRACTTEXTTOOLS EXTRACTTEXTTYPES EXTRACTTEXTEXTENSIONS
EXTRACTTEXTFLAGS EXTRACTTEXTURIS DCCREP
PYZOR DKIMIDENTITY DKIMDOMAIN DKIMSELECTOR
BAYESTC BAYESTCLEARNED BAYESTCSPAMMY BAYESTCHAMMY BAYESTC BAYESTCLEARNED BAYESTCSPAMMY BAYESTCHAMMY
HAMMYTOKENS SPAMMYTOKENS TOKENSUMMARY)) { HAMMYTOKENS SPAMMYTOKENS TOKENSUMMARY)) {
$tag_data_ref->{$_} = undef; # exist, but undefined $tag_data_ref->{$_} = undef; # exist, but undefined
@ -357,7 +360,7 @@ sub DESTROY {
########################################################################### ###########################################################################
=item $status->check () =item $status-E<gt>check ()
Runs the SpamAssassin rules against the message pointed to by the object. Runs the SpamAssassin rules against the message pointed to by the object.
@ -526,7 +529,7 @@ sub check_cleanup {
########################################################################### ###########################################################################
=item $status->learn() =item $status-E<gt>learn()
After a mail message has been checked, this method can be called. If the score After a mail message has been checked, this method can be called. If the score
is outside a certain range around the threshold, ie. if the message is judged is outside a certain range around the threshold, ie. if the message is judged
@ -624,7 +627,7 @@ sub learn_timed {
} }
} }
=item $score = $status->get_autolearn_points() =item $score = $status-E<gt>get_autolearn_points()
Return the message's score as computed for auto-learning. Certain tests are Return the message's score as computed for auto-learning. Certain tests are
ignored: ignored:
@ -647,7 +650,7 @@ sub get_autolearn_points {
return $self->{autolearn_points}; return $self->{autolearn_points};
} }
=item $score = $status->get_head_only_points() =item $score = $status-E<gt>get_head_only_points()
Return the message's score as computed for auto-learning, ignoring Return the message's score as computed for auto-learning, ignoring
all rules except for header-based ones. all rules except for header-based ones.
@ -660,7 +663,7 @@ sub get_head_only_points {
return $self->{head_only_points}; return $self->{head_only_points};
} }
=item $score = $status->get_learned_points() =item $score = $status-E<gt>get_learned_points()
Return the message's score as computed for auto-learning, ignoring Return the message's score as computed for auto-learning, ignoring
all rules except for learning-based ones. all rules except for learning-based ones.
@ -673,7 +676,7 @@ sub get_learned_points {
return $self->{learned_points}; return $self->{learned_points};
} }
=item $score = $status->get_body_only_points() =item $score = $status-E<gt>get_body_only_points()
Return the message's score as computed for auto-learning, ignoring Return the message's score as computed for auto-learning, ignoring
all rules except for body-based ones. all rules except for body-based ones.
@ -686,7 +689,7 @@ sub get_body_only_points {
return $self->{body_only_points}; return $self->{body_only_points};
} }
=item $score = $status->get_autolearn_force_status() =item $score = $status-E<gt>get_autolearn_force_status()
Return whether a message's score included any rules that are flagged as Return whether a message's score included any rules that are flagged as
autolearn_force. autolearn_force.
@ -699,7 +702,7 @@ sub get_autolearn_force_status {
return $self->{autolearn_force}; return $self->{autolearn_force};
} }
=item $rule_names = $status->get_autolearn_force_names() =item $rule_names = $status-E<gt>get_autolearn_force_names()
Return a list of comma separated list of rule names if a message's Return a list of comma separated list of rule names if a message's
score included any rules that are flagged as autolearn_force. score included any rules that are flagged as autolearn_force.
@ -856,7 +859,7 @@ sub _get_autolearn_points {
########################################################################### ###########################################################################
=item $isspam = $status->is_spam () =item $isspam = $status-E<gt>is_spam ()
After a mail message has been checked, this method can be called. It will After a mail message has been checked, this method can be called. It will
return 1 for mail determined likely to be spam, 0 if it does not seem return 1 for mail determined likely to be spam, 0 if it does not seem
@ -872,7 +875,7 @@ sub is_spam {
########################################################################### ###########################################################################
=item $list = $status->get_names_of_tests_hit () =item $list = $status-E<gt>get_names_of_tests_hit ()
After a mail message has been checked, this method can be called. It will After a mail message has been checked, this method can be called. It will
return a comma-separated string, listing all the symbolic test names return a comma-separated string, listing all the symbolic test names
@ -886,7 +889,7 @@ sub get_names_of_tests_hit {
return join(',', sort @{$self->{test_names_hit}}); return join(',', sort @{$self->{test_names_hit}});
} }
=item $list = $status->get_names_of_tests_hit_with_scores_hash () =item $list = $status-E<gt>get_names_of_tests_hit_with_scores_hash ()
After a mail message has been checked, this method can be called. It will After a mail message has been checked, this method can be called. It will
return a pointer to a hash for rule & score pairs for all the symbolic return a pointer to a hash for rule & score pairs for all the symbolic
@ -904,7 +907,7 @@ sub get_names_of_tests_hit_with_scores_hash {
return \%testsscores; return \%testsscores;
} }
=item $list = $status->get_names_of_tests_hit_with_scores () =item $list = $status-E<gt>get_names_of_tests_hit_with_scores ()
After a mail message has been checked, this method can be called. It will After a mail message has been checked, this method can be called. It will
return a comma-separated string of rule=score pairs for all the symbolic return a comma-separated string of rule=score pairs for all the symbolic
@ -924,7 +927,7 @@ sub get_names_of_tests_hit_with_scores {
########################################################################### ###########################################################################
=item $list = $status->get_names_of_subtests_hit () =item $list = $status-E<gt>get_names_of_subtests_hit ()
After a mail message has been checked, this method can be called. It will After a mail message has been checked, this method can be called. It will
return a comma-separated string, listing all the symbolic test names of the return a comma-separated string, listing all the symbolic test names of the
@ -978,7 +981,7 @@ sub get_names_of_subtests_hit {
########################################################################### ###########################################################################
=item $num = $status->get_score () =item $num = $status-E<gt>get_score ()
After a mail message has been checked, this method can be called. It will After a mail message has been checked, this method can be called. It will
return the message's score. return the message's score.
@ -998,7 +1001,7 @@ sub get_hits {
########################################################################### ###########################################################################
=item $num = $status->get_required_score () =item $num = $status-E<gt>get_required_score ()
After a mail message has been checked, this method can be called. It will After a mail message has been checked, this method can be called. It will
return the score required for a mail to be considered spam. return the score required for a mail to be considered spam.
@ -1018,7 +1021,7 @@ sub get_required_hits {
########################################################################### ###########################################################################
=item $num = $status->get_autolearn_status () =item $num = $status-E<gt>get_autolearn_status ()
After a mail message has been checked, this method can be called. It will After a mail message has been checked, this method can be called. It will
return one of the following strings depending on whether the mail was return one of the following strings depending on whether the mail was
@ -1042,7 +1045,7 @@ sub get_autolearn_status {
########################################################################### ###########################################################################
=item $report = $status->get_report () =item $report = $status-E<gt>get_report ()
Deliver a "spam report" on the checked mail message. This contains details of Deliver a "spam report" on the checked mail message. This contains details of
how many spam detection rules it triggered. how many spam detection rules it triggered.
@ -1073,7 +1076,7 @@ sub get_report {
########################################################################### ###########################################################################
=item $preview = $status->get_content_preview () =item $preview = $status-E<gt>get_content_preview ()
Give a "preview" of the content. Give a "preview" of the content.
@ -1118,7 +1121,7 @@ sub get_content_preview {
########################################################################### ###########################################################################
=item $msg = $status->get_message() =item $msg = $status-E<gt>get_message()
Return the object representing the message being scanned. Return the object representing the message being scanned.
@ -1131,7 +1134,7 @@ sub get_message {
########################################################################### ###########################################################################
=item $status->rewrite_mail () =item $status-E<gt>rewrite_mail ()
Rewrite the mail message. This will at minimum add headers, and at Rewrite the mail message. This will at minimum add headers, and at
maximum MIME-encapsulate the message text, to reflect its spam or not-spam maximum MIME-encapsulate the message text, to reflect its spam or not-spam
@ -1622,7 +1625,7 @@ sub _replace_tags {
# public API for plugins # public API for plugins
=item $status->action_depends_on_tags($tags, $code, @args) =item $status-E<gt>action_depends_on_tags($tags, $code, @args)
Enqueue the supplied subroutine reference C<$code>, to become runnable when Enqueue the supplied subroutine reference C<$code>, to become runnable when
all the specified tags become available. The C<$tags> may be a simple all the specified tags become available. The C<$tags> may be a simple
@ -1736,7 +1739,7 @@ sub report_unsatisfied_actions {
} }
} }
=item $status->set_tag($tagname, $value) =item $status-E<gt>set_tag($tagname, $value)
Set a template tag, as used in C<add_header>, report templates, etc. This Set a template tag, as used in C<add_header>, report templates, etc. This
API is intended for use by plugins. Tag names will be converted to an API is intended for use by plugins. Tag names will be converted to an
@ -1778,7 +1781,7 @@ sub set_tag {
# public API for plugins # public API for plugins
=item $string = $status->get_tag($tagname) =item $string = $status-E<gt>get_tag($tagname)
Get the current value of a template tag, as used in C<add_header>, report Get the current value of a template tag, as used in C<add_header>, report
templates, etc. This API is intended for use by plugins. Tag names will be templates, etc. This API is intended for use by plugins. Tag names will be
@ -1821,7 +1824,7 @@ sub get_tag {
return $data; return $data;
} }
=item $string = $status->get_tag_raw($tagname, @args) =item $string = $status-E<gt>get_tag_raw($tagname, @args)
Similar to C<get_tag>, but keeps a tag name unchanged (does not uppercase it), Similar to C<get_tag>, but keeps a tag name unchanged (does not uppercase it),
and does not convert arrayref tag values into a single string. and does not convert arrayref tag values into a single string.
@ -1858,7 +1861,7 @@ sub get_tag_raw {
# public API for plugins # public API for plugins
=item $status->set_spamd_result_item($subref) =item $status-E<gt>set_spamd_result_item($subref)
Set an entry for the spamd result log line. C<$subref> should be a code Set an entry for the spamd result log line. C<$subref> should be a code
reference for a subroutine which will return a string in C<'name=VALUE'> reference for a subroutine which will return a string in C<'name=VALUE'>
@ -1931,7 +1934,7 @@ sub _get_tag_value_for_required_score {
########################################################################### ###########################################################################
=item $status->finish () =item $status-E<gt>finish ()
Indicate that this C<$status> object is finished with, and can be destroyed. Indicate that this C<$status> object is finished with, and can be destroyed.
@ -1970,7 +1973,7 @@ sub finish_tests {}
########################################################################### ###########################################################################
=item $name = $status->get_current_eval_rule_name() =item $name = $status-E<gt>get_current_eval_rule_name()
Return the name of the currently-running eval rule. C<undef> is Return the name of the currently-running eval rule. C<undef> is
returned if no eval rule is currently being run. Useful for plugins returned if no eval rule is currently being run. Useful for plugins
@ -2051,7 +2054,7 @@ sub extract_message_metadata {
########################################################################### ###########################################################################
=item $status->get_decoded_body_text_array () =item $status-E<gt>get_decoded_body_text_array ()
Returns the message body, with B<base64> or B<quoted-printable> encodings Returns the message body, with B<base64> or B<quoted-printable> encodings
decoded, and non-text parts or non-inline attachments stripped. decoded, and non-text parts or non-inline attachments stripped.
@ -2067,7 +2070,7 @@ sub get_decoded_body_text_array {
return $_[0]->{msg}->get_decoded_body_text_array(); return $_[0]->{msg}->get_decoded_body_text_array();
} }
=item $status->get_decoded_stripped_body_text_array () =item $status-E<gt>get_decoded_stripped_body_text_array ()
Returns the message body, decoded (as described in Returns the message body, decoded (as described in
get_decoded_body_text_array()), with HTML rendered, and with whitespace get_decoded_body_text_array()), with HTML rendered, and with whitespace
@ -2089,14 +2092,18 @@ sub get_decoded_stripped_body_text_array {
########################################################################### ###########################################################################
=item $status->get (header_name [, default_value]) =item $status-E<gt>get (header_name [, default_value])
Returns a message header, pseudo-header or a real name, email-address or Returns message headers, pseudo-headers, names, email-addresses or some
some other parsed value set by modifiers. C<header_name> is the name of a other parsed values set by modifiers. C<header_name> is the name of a mail
mail header, such as 'Subject', 'To', etc. header such as 'Subject', 'To' etc, or a pseudo/metadata-header like 'ALL',
'X-Spam-Relays-Untrusted' etc.
Should be called in list context since 4.0. Will return list of headers Should be called in list context since SpamAssassin 4.0. This supports
content, or other values when modifiers used. returning multiple values for all header and modifier types.
If called in scalar context (pre-4.0 style), only first value is returned
for modifiers like C<:addr> or C<:name>.
If C<default_value> is given, it will be used if the requested If C<default_value> is given, it will be used if the requested
C<header_name> does not exist. This is mainly useful when called in scalar C<header_name> does not exist. This is mainly useful when called in scalar
@ -2116,17 +2123,17 @@ result in "example@foo" (and "example@bar"):
=item example@foo =item example@foo
=item example@foo (Foo Blah), <example@bar> =item example@foo (Foo Blah), E<lt>example@barE<gt>
=item example@foo, example@bar =item example@foo, example@bar
=item display: example@foo (Foo Blah), example@bar ; =item display: example@foo (Foo Blah), example@bar ;
=item Foo Blah <example@foo> =item Foo Blah E<lt>example@fooE<gt>
=item "Foo Blah" <example@foo> =item "Foo Blah" E<lt>example@fooE<gt>
=item "'Foo Blah'" <example@foo> =item "'Foo Blah'" E<lt>example@fooE<gt>
=back =back
@ -2141,15 +2148,15 @@ stripped too, as it is often seen.
=item example@foo (Foo Blah) =item example@foo (Foo Blah)
=item example@foo (Foo Blah), "Bar Baz" <example@bar> =item example@foo (Foo Blah), "Bar Baz" E<lt>example@barI<gt>
=item display: example@foo (Foo Blah), example@bar ; =item display: example@foo (Foo Blah), example@bar ;
=item Foo Blah <example@foo> =item Foo Blah E<lt>example@fooE<gt>
=item "Foo Blah" <example@foo> =item "Foo Blah" E<lt>example@fooE<gt>
=item "'Foo Blah'" <example@foo> =item "'Foo Blah'" E<lt>example@fooE<gt>
=back =back
@ -2204,10 +2211,11 @@ headers.
transaction that delivered this message, if this data has been made available transaction that delivered this message, if this data has been made available
by the SMTP server. by the SMTP server.
=item C<MESSAGEID> is a symbol meaning all Message-Id's found in the message; =item C<MESSAGEID> is a symbol meaning all Message-Id's found in the
some mailing list software moves the real 'Message-Id' to 'Resent-Message-Id' message; some mailing list software moves the real 'Message-Id' to
or 'X-Message-Id', then uses its own one in the 'Message-Id' header. The value 'Resent-Message-Id' or 'X-Message-Id' or 'X-Original-Message-ID', then uses
returned for this symbol is the text from all 3 headers, separated by newlines. its own one in the 'Message-Id' header. The value returned for this symbol
is the text from all 4 headers.
=item C<X-Spam-Relays-Untrusted> is the generated metadata of untrusted relays =item C<X-Spam-Relays-Untrusted> is the generated metadata of untrusted relays
the message has passed through the message has passed through
@ -2490,7 +2498,7 @@ sub get {
if ($_[1] =~ /:(?:addr|name|host|domain|ip|revip)\b/ || if ($_[1] =~ /:(?:addr|name|host|domain|ip|revip)\b/ ||
$_[1] eq 'EnvelopeFrom') { $_[1] eq 'EnvelopeFrom') {
my $res = $found->[0]; my $res = $found->[0];
$res =~ s/\n\z$//; $res =~ s/\n\z//;
return $res; return $res;
} else { } else {
return join('', @$found); return join('', @$found);
@ -2569,12 +2577,12 @@ sub _tbirdurire {
return $self->{tbirdurire}; return $self->{tbirdurire};
} }
=item $status->get_uri_list () =item $status-E<gt>get_uri_list ()
Returns an array of all unique URIs found in the message. It takes Returns an array of all unique URIs found in the message. It takes
a combination of the URIs found in the rendered (decoded and HTML a combination of the URIs found in the rendered (decoded and HTML
stripped) body and the URIs found when parsing the HTML in the message. stripped) body and the URIs found when parsing the HTML in the message.
Will also set $status->{uri_list} (the array as returned by this function). Will also set $status-E<gt>{uri_list} (the array as returned by this function).
The returned array will include the "raw" URI as well as The returned array will include the "raw" URI as well as
"slightly cooked" versions. For example, the single URI "slightly cooked" versions. For example, the single URI
@ -2614,13 +2622,13 @@ sub get_uri_list {
return @{$self->{uri_list}}; return @{$self->{uri_list}};
} }
=item $status->get_uri_detail_list () =item $status-E<gt>get_uri_detail_list ()
Returns a hash reference of all unique URIs found in the message and Returns a hash reference of all unique URIs found in the message and
various data about where the URIs were found in the message. It takes a various data about where the URIs were found in the message. It takes a
combination of the URIs found in the rendered (decoded and HTML stripped) combination of the URIs found in the rendered (decoded and HTML stripped)
body and the URIs found when parsing the HTML in the message. Will also body and the URIs found when parsing the HTML in the message. Will also
set $status->{uri_detail_list} (the hash reference as returned by this set $status-E<gt>{uri_detail_list} (the hash reference as returned by this
function). function).
The hash format looks something like this: The hash format looks something like this:
@ -2649,8 +2657,8 @@ linkifying (i.e. email address found in body without mailto:).
C<cleaned> is an array of the raw and canonicalized version of the raw_uri C<cleaned> is an array of the raw and canonicalized version of the raw_uri
(http://spamassassin.apache%2Eorg/, https://spamassassin.apache.org/). (http://spamassassin.apache%2Eorg/, https://spamassassin.apache.org/).
C<anchor_text> is an array of the anchor text (text between <a> and C<anchor_text> is an array of the anchor text (text between E<lt>aE<gt> and
</a>), if any, which linked to the URI. E<lt>/aE<gt>), if any, which linked to the URI.
C<domains> is a hash of the domains found in the canonicalized URIs. C<domains> is a hash of the domains found in the canonicalized URIs.
@ -2835,7 +2843,7 @@ sub _process_dkim_uri_list {
} }
} }
=item $status->add_uri_detail_list ($raw_uri, $types, $source, $valid_domain) =item $status-E<gt>add_uri_detail_list ($raw_uri, $types, $source, $valid_domain)
Adds values to internal uri_detail_list. When used from Plugins, recommended Adds values to internal uri_detail_list. When used from Plugins, recommended
to call from parsed_metadata (along with register_method_priority, -10) so to call from parsed_metadata (along with register_method_priority, -10) so
@ -2844,10 +2852,10 @@ other Plugins calling get_uri_detail_list() will see it.
C<raw_uri> is the URI to be added. The only required parameter. C<raw_uri> is the URI to be added. The only required parameter.
C<types> is an optional hash reference, contents are added to C<types> is an optional hash reference, contents are added to
uri_detail_list->{types} (see get_uri_detail_list for known keys). uri_detail_list-E<gt>{types} (see get_uri_detail_list for known keys).
I<parsed> is default is no hash given. I<nocanon> does not run I<parsed> is default is no hash given. I<nocanon> does not run
uri_list_canonicalize (no redirector, uri fixing). I<noclean> skips adding uri_list_canonicalize (no redirector, uri fixing). I<noclean> skips adding
uri_detail_list->{cleaned}, so it would not be used in "uri" rule checks, uri_detail_list-E<gt>{cleaned}, so it would not be used in "uri" rule checks,
but domain/hosts would still be used for URIBL/RBL purposes. but domain/hosts would still be used for URIBL/RBL purposes.
C<source> is an optional simple string, only used for debug logging purposes C<source> is an optional simple string, only used for debug logging purposes
@ -2979,7 +2987,7 @@ ENDOFEVAL
########################################################################### ###########################################################################
=item $status->clear_test_state() =item $status-E<gt>clear_test_state()
DEPRECATED, UNNEEDED SINCE 4.0 DEPRECATED, UNNEEDED SINCE 4.0
@ -3037,7 +3045,7 @@ sub _wrap_desc {
########################################################################### ###########################################################################
=item $status->got_hit ($rulename, $desc_prepend [, name => value, ...]) =item $status-E<gt>got_hit ($rulename, $desc_prepend [, name =E<gt> value, ...])
Register a hit against a rule in the ruleset. Register a hit against a rule in the ruleset.
@ -3050,37 +3058,37 @@ data:
=over 4 =over 4
=item score => $num =item score =E<gt> $num
Optional: the score to use for the rule hit. If unspecified, Optional: the score to use for the rule hit. If unspecified,
the value from the C<Mail::SpamAssassin::Conf> object's C<{scores}> the value from the C<Mail::SpamAssassin::Conf> object's C<{scores}>
hash will be used (a configured score), and in its absence the hash will be used (a configured score), and in its absence the
C<defscore> option value. C<defscore> option value.
=item defscore => $num =item defscore =E<gt> $num
Optional: the score to use for the rule hit if neither the Optional: the score to use for the rule hit if neither the
option C<score> is provided, nor a configured score value is provided. option C<score> is provided, nor a configured score value is provided.
=item value => $num =item value =E<gt> $num
Optional: the value to assign to the rule; the default value is C<1>. Optional: the value to assign to the rule; the default value is C<1>.
I<tflags multiple> rules use values of greater than 1 to indicate I<tflags multiple> rules use values of greater than 1 to indicate
multiple hits. This value is accessible to meta rules. multiple hits. This value is accessible to meta rules.
=item ruletype => $type =item ruletype =E<gt> $type
Optional, but recommended: the rule type string. This is used in the Optional, but recommended: the rule type string. This is used in the
C<hit_rule> plugin call, called by this method. If unset, I<'unknown'> is C<hit_rule> plugin call, called by this method. If unset, I<'unknown'> is
used. used.
=item tflags => $string =item tflags =E<gt> $string
Optional: a string, i.e. a space-separated list of additional tflags Optional: a string, i.e. a space-separated list of additional tflags
to be appended to an existing list of flags in $self->{conf}->{tflags}, to be appended to an existing list of flags in $self-E<gt>{conf}-E<gt>{tflags},
such as: "nice noautolearn multiple". No syntax checks are performed. such as: "nice noautolearn multiple". No syntax checks are performed.
=item description => $string =item description =E<gt> $string
Optional: a custom rule description string. This is used in the Optional: a custom rule description string. This is used in the
C<hit_rule> plugin call, called by this method. If unset, the static C<hit_rule> plugin call, called by this method. If unset, the static
@ -3089,7 +3097,7 @@ description is used.
=back =back
Backward compatibility: the two mandatory arguments have been part of this API Backward compatibility: the two mandatory arguments have been part of this API
since SpamAssassin 2.x. The optional I<name=<gt>value> pairs, however, are a since SpamAssassin 2.x. The optional C<name=E<gt>value> pairs, however, are a
new addition in SpamAssassin 3.2.0. new addition in SpamAssassin 3.2.0.
=cut =cut
@ -3173,13 +3181,13 @@ sub got_hit {
return 1; return 1;
} }
=item $status->rule_ready ($rulename [, $no_async]) =item $status-E<gt>rule_ready ($rulename [, $no_async])
Mark an asynchronous rule ready, so it can be considered for meta rule Mark an asynchronous rule ready, so it can be considered for meta rule
evaluation. Asynchronous rule is a rule whose eval-function returns undef, evaluation. Asynchronous rule is a rule whose eval-function returns undef,
marking that it's not ready yet, expecting results later. marking that it's not ready yet, expecting results later.
$status->rule_ready() must be called later to mark it ready, alternatively $status-E<gt>rule_ready() must be called later to mark it ready, alternatively
$status->got_hit() also does this. If neither is called, then any meta rule $status-E<gt>got_hit() also does this. If neither is called, then any meta rule
that depends on this rule might not evaluate. that depends on this rule might not evaluate.
Optional boolean $no_async skips checking if there are pending async DNS Optional boolean $no_async skips checking if there are pending async DNS
@ -3209,12 +3217,12 @@ sub rule_ready {
########################################################################### ###########################################################################
=item $status->test_log ($text [, $rulename]) =item $status-E<gt>test_log ($text [, $rulename])
Add $text log entry for a hit rule in final message REPORT/SUMMARY. Add $text log entry for a hit rule in final message REPORT/SUMMARY.
Usually called just before got_hit(), to describe for example what URI the Usually called just before got_hit(), to describe for example what URI the
rule matched on. Optional <$rulename> argument is recommended to make sure rule matched on. Optional C<$rulename> argument is recommended to make sure
log is written to correct rule. If rulename is not provided, log is written to correct rule. If rulename is not provided,
get_current_eval_rule_name() is used as fallback. get_current_eval_rule_name() is used as fallback.
@ -3431,7 +3439,7 @@ sub sa_die { Mail::SpamAssassin::sa_die(@_); }
########################################################################### ###########################################################################
=item $status->create_fulltext_tmpfile (fulltext_ref) =item $status-E<gt>create_fulltext_tmpfile (fulltext_ref)
This function creates a temporary file containing the passed scalar This function creates a temporary file containing the passed scalar
reference data. If no scalar is passed, full/pristine message text is reference data. If no scalar is passed, full/pristine message text is
@ -3480,9 +3488,9 @@ sub create_fulltext_tmpfile {
return $tmpf; return $tmpf;
} }
=item $status->delete_fulltext_tmpfile (tmpfile) =item $status-E<gt>delete_fulltext_tmpfile (tmpfile)
Will cleanup after a $status->create_fulltext_tmpfile() call. Deletes the Will cleanup after a $status-E<gt>create_fulltext_tmpfile() call. Deletes the
temporary file and uncaches the filename. Generally there no need to call temporary file and uncaches the filename. Generally there no need to call
this, PerMsgStatus destructor cleans up all tmpfiles. this, PerMsgStatus destructor cleans up all tmpfiles.

View File

@ -55,7 +55,7 @@ our @ISA = qw();
########################################################################### ###########################################################################
=item $factory = PersistentAddrListSubclass->new(); =item $factory = PersistentAddrListSubclass-E<gt>new();
This creates a factory object, which SpamAssassin will call to create This creates a factory object, which SpamAssassin will call to create
a new checker object for the persistent address list. a new checker object for the persistent address list.
@ -72,7 +72,7 @@ sub new {
########################################################################### ###########################################################################
=item my $addrlist = $factory->new_checker(); =item my $addrlist = $factory-E<gt>new_checker();
Create a new address-list checker object from the factory. Called by the Create a new address-list checker object from the factory. Called by the
SpamAssassin classes. SpamAssassin classes.
@ -86,7 +86,7 @@ sub new_checker {
########################################################################### ###########################################################################
=item $entry = $addrlist->get_addr_entry ($addr); =item $entry = $addrlist-E<gt>get_addr_entry ($addr);
Given an email address C<$addr>, return an entry object with the details of Given an email address C<$addr>, return an entry object with the details of
that address. that address.
@ -115,7 +115,7 @@ sub get_addr_entry {
########################################################################### ###########################################################################
=item $entry = $addrlist->add_score($entry, $score); =item $entry = $addrlist-E<gt>add_score($entry, $score);
This method should add the given score to the welcomelist database for the This method should add the given score to the welcomelist database for the
given entry, and then return the new entry. given entry, and then return the new entry.
@ -129,7 +129,7 @@ sub add_score {
########################################################################### ###########################################################################
=item $entry = $addrlist->remove_entry ($entry); =item $entry = $addrlist-E<gt>remove_entry ($entry);
This method should remove the given entry from the welcomelist database. This method should remove the given entry from the welcomelist database.
@ -142,7 +142,7 @@ sub remove_entry {
########################################################################### ###########################################################################
=item $entry = $addrlist->finish (); =item $entry = $addrlist-E<gt>finish ();
Clean up, if necessary. Called by SpamAssassin when it has finished Clean up, if necessary. Called by SpamAssassin when it has finished
checking, or adding to, the auto-welcomelist database. checking, or adding to, the auto-welcomelist database.

View File

@ -108,7 +108,7 @@ our @ISA = qw();
########################################################################### ###########################################################################
=item $plugin = MyPluginClass->new ($mailsaobject) =item $plugin = MyPluginClass-E<gt>new ($mailsaobject)
Constructor. Plugins that need to register themselves will need to Constructor. Plugins that need to register themselves will need to
define their own; the default super-class constructor will work fine define their own; the default super-class constructor will work fine
@ -149,7 +149,7 @@ sub new {
# the object and determine if it's capable of calling the method anyway. # the object and determine if it's capable of calling the method anyway.
# Nifty! # Nifty!
=item $plugin->parse_config ( { options ... } ) =item $plugin-E<gt>parse_config ( { options ... } )
Parse a configuration line that hasn't already been handled. C<options> Parse a configuration line that hasn't already been handled. C<options>
is a reference to a hash containing these options: is a reference to a hash containing these options:
@ -195,7 +195,7 @@ That can be found as C<$plugin-E<gt>{main}-E<gt>{conf}>, or as "conf" in the
C<$options> hash reference above. By storing it on C<conf>, this allows C<$options> hash reference above. By storing it on C<conf>, this allows
per-user and system-wide configuration precedence to be dealt with correctly. per-user and system-wide configuration precedence to be dealt with correctly.
=item $plugin->finish_parsing_start ( { options ... } ) =item $plugin-E<gt>finish_parsing_start ( { options ... } )
Signals that the system-wide configuration has been completely read, Signals that the system-wide configuration has been completely read,
but internal data structures are not yet created. It is possible to but internal data structures are not yet created. It is possible to
@ -219,7 +219,7 @@ this plugin hook, if you modify the rules data structures in a
third-party plugin, all bets are off until such time that an API is third-party plugin, all bets are off until such time that an API is
present for modifying that configuration data. present for modifying that configuration data.
=item $plugin->finish_parsing_end ( { options ... } ) =item $plugin-E<gt>finish_parsing_end ( { options ... } )
Signals that the system-wide configuration parsing has just finished, and Signals that the system-wide configuration parsing has just finished, and
SpamAssassin is nearly ready to check messages. SpamAssassin is nearly ready to check messages.
@ -241,7 +241,7 @@ this plugin hook, if you modify the rules data structures in a
third-party plugin, all bets are off until such time that an API is third-party plugin, all bets are off until such time that an API is
present for modifying that configuration data. present for modifying that configuration data.
=item $plugin->user_conf_parsing_start ( { options ... } ) =item $plugin-E<gt>user_conf_parsing_start ( { options ... } )
Signals that the per-user configuration has been completely read, but Signals that the per-user configuration has been completely read, but
not converted to internal data structures. It is possible to use this not converted to internal data structures. It is possible to use this
@ -267,7 +267,7 @@ this plugin hook, if you modify the rules data structures in a
third-party plugin, all bets are off until such time that an API is third-party plugin, all bets are off until such time that an API is
present for modifying that configuration data. present for modifying that configuration data.
=item $plugin->user_conf_parsing_end ( { options ... } ) =item $plugin-E<gt>user_conf_parsing_end ( { options ... } )
Signals that the per-user configuration parsing has just finished, and Signals that the per-user configuration parsing has just finished, and
SpamAssassin is nearly ready to check messages. If C<allow_user_rules> is SpamAssassin is nearly ready to check messages. If C<allow_user_rules> is
@ -291,7 +291,7 @@ this plugin hook, if you modify the rules data structures in a
third-party plugin, all bets are off until such time that an API is third-party plugin, all bets are off until such time that an API is
present for modifying that configuration data. present for modifying that configuration data.
=item $plugin->signal_user_changed ( { options ... } ) =item $plugin-E<gt>signal_user_changed ( { options ... } )
Signals that the current user has changed for a new one. Signals that the current user has changed for a new one.
@ -311,7 +311,7 @@ The new user's storage directory. (equivalent to C<~/.spamassassin>.)
=back =back
=item $plugin->services_authorized_for_username ( { options ... } ) =item $plugin-E<gt>services_authorized_for_username ( { options ... } )
Validates that a given username is authorized to use certain services. Validates that a given username is authorized to use certain services.
@ -344,7 +344,7 @@ data should be stored.
=back =back
=item $plugin->compile_now_start ( { options ... } ) =item $plugin-E<gt>compile_now_start ( { options ... } )
This is called at the beginning of Mail::SpamAssassin::compile_now() so This is called at the beginning of Mail::SpamAssassin::compile_now() so
plugins can do any necessary initialization for multi-process plugins can do any necessary initialization for multi-process
@ -362,7 +362,7 @@ The value of $keep_userstate option in compile_now().
=back =back
=item $plugin->compile_now_finish ( { options ... } ) =item $plugin-E<gt>compile_now_finish ( { options ... } )
This is called at the end of Mail::SpamAssassin::compile_now() so This is called at the end of Mail::SpamAssassin::compile_now() so
plugins can do any necessary initialization for multi-process plugins can do any necessary initialization for multi-process
@ -380,7 +380,7 @@ The value of $keep_userstate option in compile_now().
=back =back
=item $plugin->check_start ( { options ... } ) =item $plugin-E<gt>check_start ( { options ... } )
Signals that a message check operation is starting. Signals that a message check operation is starting.
@ -400,7 +400,7 @@ APIs on that object, too. See C<Mail::SpamAssassin::PerMsgStatus> perldoc.
=back =back
=item $plugin->check_main ( { options ... } ) =item $plugin-E<gt>check_main ( { options ... } )
Signals that a message should be checked. Note that implementations of Signals that a message should be checked. Note that implementations of
this hook should return C<1>. this hook should return C<1>.
@ -413,7 +413,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->check_tick ( { options ... } ) =item $plugin-E<gt>check_tick ( { options ... } )
Called periodically during a message check operation. A callback set for Called periodically during a message check operation. A callback set for
this method is a good place to run through an event loop dealing with this method is a good place to run through an event loop dealing with
@ -427,7 +427,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->check_dnsbl ( { options ... } ) =item $plugin-E<gt>check_dnsbl ( { options ... } )
Called when DNSBL or other network lookups are being launched, implying Called when DNSBL or other network lookups are being launched, implying
current running priority of -100. This is the place to start your own current running priority of -100. This is the place to start your own
@ -441,7 +441,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->check_post_dnsbl ( { options ... } ) =item $plugin-E<gt>check_post_dnsbl ( { options ... } )
Called after the DNSBL results have been harvested. This is a good Called after the DNSBL results have been harvested. This is a good
place to harvest your own asynchronously-started network lookups. place to harvest your own asynchronously-started network lookups.
@ -454,7 +454,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->check_cleanup ( { options ... } ) =item $plugin-E<gt>check_cleanup ( { options ... } )
Called just before message check is finishing and before possible Called just before message check is finishing and before possible
auto-learning. This is guaranteed to be always called, unlike check_tick auto-learning. This is guaranteed to be always called, unlike check_tick
@ -469,7 +469,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->check_post_learn ( { options ... } ) =item $plugin-E<gt>check_post_learn ( { options ... } )
Called after auto-learning may (or may not) have taken place. If you Called after auto-learning may (or may not) have taken place. If you
wish to perform additional learning, whether or not auto-learning wish to perform additional learning, whether or not auto-learning
@ -483,7 +483,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->check_end ( { options ... } ) =item $plugin-E<gt>check_end ( { options ... } )
Signals that a message check operation has just finished, and the Signals that a message check operation has just finished, and the
results are about to be returned to the caller. results are about to be returned to the caller.
@ -498,7 +498,7 @@ using the public APIs on this object.
=back =back
=item $plugin->finish_tests ( { options ... } ) =item $plugin-E<gt>finish_tests ( { options ... } )
Called via C<Mail::SpamAssassin::finish>. This should clear up any tests that Called via C<Mail::SpamAssassin::finish>. This should clear up any tests that
a plugin has added to the namespace. a plugin has added to the namespace.
@ -521,7 +521,7 @@ data should be stored.
See also the C<register_generated_rule_method> helper API, below. See also the C<register_generated_rule_method> helper API, below.
=item $plugin->extract_metadata ( { options ... } ) =item $plugin-E<gt>extract_metadata ( { options ... } )
Signals that a message is being mined for metadata. Some plugins may wish Signals that a message is being mined for metadata. Some plugins may wish
to add their own metadata as well. to add their own metadata as well.
@ -538,7 +538,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->parsed_metadata ( { options ... } ) =item $plugin-E<gt>parsed_metadata ( { options ... } )
Signals that a message's metadata has been parsed, and can now be Signals that a message's metadata has been parsed, and can now be
accessed by the plugin. accessed by the plugin.
@ -551,7 +551,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->start_rules ( { options ... } ) =item $plugin-E<gt>start_rules ( { options ... } )
Called before testing a set of rules of a given type and priority. Called before testing a set of rules of a given type and priority.
@ -571,7 +571,7 @@ The priority level of the rules about to be performed.
=back =back
=item $plugin->hit_rule ( { options ... } ) =item $plugin-E<gt>hit_rule ( { options ... } )
Called when a rule fires. Called when a rule fires.
@ -595,7 +595,7 @@ The rule's score in the active scoreset.
=back =back
=item $plugin->ran_rule ( { options ... } ) =item $plugin-E<gt>ran_rule ( { options ... } )
Called after a rule has been tested, whether or not it fired. When the Called after a rule has been tested, whether or not it fired. When the
rule fires, the hit_rule callback is always called before this. rule fires, the hit_rule callback is always called before this.
@ -616,7 +616,7 @@ The name of the rule that was tested.
=back =back
=item $plugin->autolearn_discriminator ( { options ... } ) =item $plugin-E<gt>autolearn_discriminator ( { options ... } )
Control whether a just-scanned message should be learned as either Control whether a just-scanned message should be learned as either
spam or ham. This method should return one of C<1> to learn spam or ham. This method should return one of C<1> to learn
@ -631,7 +631,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->autolearn ( { options ... } ) =item $plugin-E<gt>autolearn ( { options ... } )
Signals that a message is about to be auto-learned as either ham or spam. Signals that a message is about to be auto-learned as either ham or spam.
@ -647,7 +647,7 @@ C<1> if the message is spam, C<0> if ham.
=back =back
=item $plugin->per_msg_finish ( { options ... } ) =item $plugin-E<gt>per_msg_finish ( { options ... } )
Signals that a C<Mail::SpamAssassin::PerMsgStatus> object is being Signals that a C<Mail::SpamAssassin::PerMsgStatus> object is being
destroyed, and any per-scan context held on that object by this destroyed, and any per-scan context held on that object by this
@ -667,7 +667,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->have_shortcircuited ( { options ... } ) =item $plugin-E<gt>have_shortcircuited ( { options ... } )
Has the current scan operation 'short-circuited'? In other words, can Has the current scan operation 'short-circuited'? In other words, can
further scanning be skipped, since the message is already definitively further scanning be skipped, since the message is already definitively
@ -684,7 +684,7 @@ The C<Mail::SpamAssassin::PerMsgStatus> context object for this scan.
=back =back
=item $plugin->bayes_learn ( { options ... } ) =item $plugin-E<gt>bayes_learn ( { options ... } )
Called at the end of a bayes learn operation. Called at the end of a bayes learn operation.
@ -709,7 +709,7 @@ are a single string containing the raw token value. You can test for
backward compatibility by checking to see if the value for a key is a backward compatibility by checking to see if the value for a key is a
reference to a perl HASH, for instance: reference to a perl HASH, for instance:
if (ref($toksref->{$sometokenkey}) eq 'HASH') {... if (ref($toksref-E<gt>{$sometokenkey}) eq 'HASH') {...
If it is, then you are using the old interface, otherwise you are using If it is, then you are using the old interface, otherwise you are using
the current interface. the current interface.
@ -732,7 +732,7 @@ could not be determined. In addition, if the receive date is more than
=back =back
=item $plugin->bayes_forget ( { options ... } ) =item $plugin-E<gt>bayes_forget ( { options ... } )
Called at the end of a bayes forget operation. Called at the end of a bayes forget operation.
@ -755,7 +755,7 @@ Generated message id of the message just forgotten.
=back =back
=item $plugin->bayes_scan ( { options ... } ) =item $plugin-E<gt>bayes_scan ( { options ... } )
Called at the end of a bayes scan operation. NOTE: Will not be Called at the end of a bayes scan operation. NOTE: Will not be
called in case of error or if the message is otherwise skipped. called in case of error or if the message is otherwise skipped.
@ -797,7 +797,7 @@ this message.
=back =back
=item $plugin->plugin_report ( { options ... } ) =item $plugin-E<gt>plugin_report ( { options ... } )
Called if the message is to be reported as spam. If the reporting system is Called if the message is to be reported as spam. If the reporting system is
available, the variable C<$options-E<gt>{report}-E<gt>report_available}> should available, the variable C<$options-E<gt>{report}-E<gt>report_available}> should
@ -821,7 +821,7 @@ Reference to the original message object.
=back =back
=item $plugin->plugin_revoke ( { options ... } ) =item $plugin-E<gt>plugin_revoke ( { options ... } )
Called if the message is to be reported as ham (revokes a spam report). If the Called if the message is to be reported as ham (revokes a spam report). If the
reporting system is available, the variable reporting system is available, the variable
@ -846,7 +846,7 @@ Reference to the original message object.
=back =back
=item $plugin->welcomelist_address( { options ... } ) =item $plugin-E<gt>welcomelist_address( { options ... } )
Previously whitelist_address which will work interchangeably until 4.1. Previously whitelist_address which will work interchangeably until 4.1.
@ -865,7 +865,7 @@ Indicate if the call is being made from a command line interface.
=back =back
=item $plugin->blocklist_address( { options ... } ) =item $plugin-E<gt>blocklist_address( { options ... } )
Previously blacklist_address which will work interchangeably until 4.1. Previously blacklist_address which will work interchangeably until 4.1.
@ -884,7 +884,7 @@ Indicate if the call is being made from a command line interface.
=back =back
=item $plugin->remove_address( { options ... } ) =item $plugin-E<gt>remove_address( { options ... } )
Called when a request is made to remove an address to a Called when a request is made to remove an address to a
persistent address list. persistent address list.
@ -901,11 +901,11 @@ Indicate if the call is being made from a command line interface.
=back =back
=item $plugin->spamd_child_init () =item $plugin-E<gt>spamd_child_init ()
Called in each new child process when it starts up under spamd. Called in each new child process when it starts up under spamd.
=item $plugin->log_scan_result ( { options ... } ) =item $plugin-E<gt>log_scan_result ( { options ... } )
Called when spamd has completed scanning a message. Currently, Called when spamd has completed scanning a message. Currently,
only spamd calls this API. only spamd calls this API.
@ -919,14 +919,14 @@ at B<https://wiki.apache.org/spamassassin/SpamdSyslogFormat>.
=back =back
=item $plugin->spamd_child_post_connection_close () =item $plugin-E<gt>spamd_child_post_connection_close ()
Called when child returns from handling a connection. Called when child returns from handling a connection.
If there was an accept failure, the child will die and this code will If there was an accept failure, the child will die and this code will
not be called. not be called.
=item $plugin->finish () =item $plugin-E<gt>finish ()
Called when the C<Mail::SpamAssassin> object is destroyed. Called when the C<Mail::SpamAssassin> object is destroyed.
@ -937,13 +937,13 @@ sub finish {
%{$self} = (); %{$self} = ();
} }
=item $plugin->learner_new () =item $plugin-E<gt>learner_new ()
Used to support human-trained probabilistic classifiers like the BAYES_* ruleset. Used to support human-trained probabilistic classifiers like the BAYES_* ruleset.
Called when a new C<Mail::SpamAssassin::Bayes> object has been created; typically Called when a new C<Mail::SpamAssassin::Bayes> object has been created; typically
when a new user's scan is about to start. when a new user's scan is about to start.
=item $plugin->learn_message () =item $plugin-E<gt>learn_message ()
Train the classifier with a training message. Train the classifier with a training message.
@ -964,7 +964,7 @@ If it is C<undef>, one will be generated. It should be unique to that message.
=back =back
=item $plugin->forget_message () =item $plugin-E<gt>forget_message ()
Tell the classifier to 'forget' its training about a specific message. Tell the classifier to 'forget' its training about a specific message.
@ -981,7 +981,7 @@ If it is C<undef>, one will be generated. It should be unique to that message.
=back =back
=item $plugin->learner_sync () =item $plugin-E<gt>learner_sync ()
Tell the classifier to 'sync' any pending changes against the current Tell the classifier to 'sync' any pending changes against the current
user's training database. This is called by C<sa-learn --sync>. user's training database. This is called by C<sa-learn --sync>.
@ -989,7 +989,7 @@ user's training database. This is called by C<sa-learn --sync>.
If you do not need to implement these for your classifier, create an If you do not need to implement these for your classifier, create an
implementation that just contains C<return 1>. implementation that just contains C<return 1>.
=item $plugin->learner_expire_old_training () =item $plugin-E<gt>learner_expire_old_training ()
Tell the classifier to perform infrequent, time-consuming cleanup of Tell the classifier to perform infrequent, time-consuming cleanup of
the current user's training database. This is called by C<sa-learn the current user's training database. This is called by C<sa-learn
@ -998,12 +998,12 @@ the current user's training database. This is called by C<sa-learn
If you do not need to implement these for your classifier, create an If you do not need to implement these for your classifier, create an
implementation that just contains C<return 1>. implementation that just contains C<return 1>.
=item $plugin->learner_is_scan_available () =item $plugin-E<gt>learner_is_scan_available ()
Should return 1 if it is possible to use the current user's training data for Should return 1 if it is possible to use the current user's training data for
a message-scan operation, or 0 otherwise. a message-scan operation, or 0 otherwise.
=item $plugin->learner_dump_database () =item $plugin-E<gt>learner_dump_database ()
Dump information about the current user's training data to C<stdout>. Dump information about the current user's training data to C<stdout>.
This is called by C<sa-learn --dump>. This is called by C<sa-learn --dump>.
@ -1025,7 +1025,7 @@ subset of the tokens to dump.
=back =back
=item $plugin->learner_close () =item $plugin-E<gt>learner_close ()
Close any open databases. Close any open databases.
@ -1046,7 +1046,7 @@ to receive specific events, or control the callback chain behaviour.
=over 4 =over 4
=item $plugin->register_eval_rule ($nameofevalsub, $ruletype) =item $plugin-E<gt>register_eval_rule ($nameofevalsub, $ruletype)
Plugins that implement an eval test will need to call this, so that Plugins that implement an eval test will need to call this, so that
SpamAssassin calls into the object when that eval test is encountered. SpamAssassin calls into the object when that eval test is encountered.
@ -1069,7 +1069,7 @@ sub register_eval_rule {
$self->{main}->{conf}->register_eval_rule ($self, $nameofsub, $ruletype); $self->{main}->{conf}->register_eval_rule ($self, $nameofsub, $ruletype);
} }
=item $plugin->register_generated_rule_method ($nameofsub) =item $plugin-E<gt>register_generated_rule_method ($nameofsub)
In certain circumstances, plugins may find it useful to compile In certain circumstances, plugins may find it useful to compile
perl functions from the ruleset, on the fly. It is important to perl functions from the ruleset, on the fly. It is important to
@ -1093,7 +1093,7 @@ sub register_generated_rule_method {
$nameofsub; $nameofsub;
} }
=item $plugin->register_method_priority($methodname, $priority) =item $plugin-E<gt>register_method_priority($methodname, $priority)
Indicate that the method named C<$methodname> on the current object Indicate that the method named C<$methodname> on the current object
has a callback priority of C<$priority>. has a callback priority of C<$priority>.
@ -1117,7 +1117,7 @@ sub register_method_priority {
$self->{method_priority}->{$methname} = $pri; $self->{method_priority}->{$methname} = $pri;
} }
=item $plugin->inhibit_further_callbacks() =item $plugin-E<gt>inhibit_further_callbacks()
Tells the plugin handler to inhibit calling into other plugins in the plugin Tells the plugin handler to inhibit calling into other plugins in the plugin
chain for the current callback. Frequently used when parsing configuration chain for the current callback. Frequently used when parsing configuration
@ -1145,7 +1145,7 @@ Output a debugging message C<$message>, if the SpamAssassin object is running
with debugging turned on. with debugging turned on.
I<NOTE:> This function is not available in the package namespace I<NOTE:> This function is not available in the package namespace
of general plugins and can't be called via $self->dbg(). If a of general plugins and can't be called via $self-E<gt>dbg(). If a
plugin wishes to output debug information, it should call plugin wishes to output debug information, it should call
C<Mail::SpamAssassin::Plugin::dbg($msg)>. C<Mail::SpamAssassin::Plugin::dbg($msg)>.
@ -1155,7 +1155,7 @@ Output an informational message C<$message>, if the SpamAssassin object
is running with informational messages turned on. is running with informational messages turned on.
I<NOTE:> This function is not available in the package namespace I<NOTE:> This function is not available in the package namespace
of general plugins and can't be called via $self->info(). If a of general plugins and can't be called via $self-E<gt>info(). If a
plugin wishes to output debug information, it should call plugin wishes to output debug information, it should call
C<Mail::SpamAssassin::Plugin::info($msg)>. C<Mail::SpamAssassin::Plugin::info($msg)>.

View File

@ -78,7 +78,7 @@ Autonomous System Number (ASN) of the connecting IP address.
=head1 DESCRIPTION =head1 DESCRIPTION
This plugin uses DNS lookups to the services of an external DNS zone such This plugin uses DNS lookups to the services of an external DNS zone such
as at C<http://www.routeviews.org/> to do the actual work. Please make as at C<https://www.routeviews.org/> to do the actual work. Please make
sure that your use of the plugin does not overload their infrastructure - sure that your use of the plugin does not overload their infrastructure -
this generally means that B<you should not use this plugin in a this generally means that B<you should not use this plugin in a
high-volume environment> or that you should use a local mirror of the high-volume environment> or that you should use a local mirror of the
@ -111,6 +111,90 @@ through the I<asn_prefix> directive and may be set to an empty string.
C<_ASNCIDR_> is not available with local GeoDB ASN lookups. C<_ASNCIDR_> is not available with local GeoDB ASN lookups.
=head1 USER SETTINGS
=over 4
=item clear_asn_lookups
Removes all previously declared I<asn_lookup> or I<asn_lookup_ipv6> entries
from the list of queries.
=item asn_prefix 'prefix_string' (default: 'AS')
The string specified in the argument is prepended to each ASN when storing
it as a tag. This prefix is rather redundant, but its default value 'AS'
is kept for backward compatibility with versions of SpamAssassin earlier
than 3.4.0. A sensible setting is an empty string. The argument may be (but
need not be) enclosed in single or double quotes for clarity.
=back
=head1 RULE DEFINITIONS AND PRIVILEGED SETTINGS
=over 4
=item asn_lookup asn-zone.example.com [ _ASNTAG_ _ASNCIDRTAG_ ]
Use this to lookup the ASN info in the specified zone for the first external
IPv4 address and add the AS number to the first specified tag and routing info
to the second specified tag.
If no tags are specified the AS number will be added to the _ASN_ tag and the
routing info will be added to the _ASNCIDR_ tag. You must specify either none
or both of the tag names. Tag names must start and end with an underscore.
If two or more I<asn_lookup>s use the same set of template tags, the results of
their lookups will be appended to each other in the template tag values in no
particular order. Duplicate results will be omitted when combining results.
In a similar fashion, you can also use the same template tag for both the AS
number tag and the routing info tag.
Examples:
asn_lookup asn.routeviews.org
asn_lookup asn.routeviews.org _ASN_ _ASNCIDR_
asn_lookup myview.example.com _MYASN_ _MYASNCIDR_
asn_lookup asn.routeviews.org _COMBINEDASN_ _COMBINEDASNCIDR_
asn_lookup myview.example.com _COMBINEDASN_ _COMBINEDASNCIDR_
asn_lookup in1tag.example.net _ASNDATA_ _ASNDATA_
=item asn_lookup_ipv6 asn-zone6.example.com [_ASN_ _ASNCIDR_]
Use specified zone for lookups of IPv6 addresses. If zone supports both
IPv4 and IPv6 queries, use both asn_lookup and asn_lookup_ipv6 for the same
zone.
=back
=head1 ADMINISTRATOR SETTINGS
=over 4
=item asn_use_geodb ( 0 / 1 ) (default: 1)
Use Mail::SpamAssassin::GeoDB module to lookup ASN numbers. You need
suitable supported module like GeoIP2 or GeoIP with ISP or ASN database
installed (for example, add EditionIDs GeoLite2-ASN in GeoIP.conf for
geoipupdate program).
GeoDB can only set _ASN_ tag, it has no data for _ASNCIDR_. If you need
both, then set asn_prefer_geodb 0 so DNS rules are tried.
=item asn_prefer_geodb ( 0 / 1 ) (default: 1)
If set, DNS lookups (asn_lookup rules) will not be run if GeoDB successfully
finds ASN. Set this to 0 to get _ASNCIDR_ even if GeoDB finds _ASN_.
=item asn_use_dns ( 0 / 1 ) (default: 1)
Set to 0 to never allow DNS queries.
=back
=head1 BAYES =head1 BAYES
The bayes tokenizer will use ASN data for bayes calculations, and thus The bayes tokenizer will use ASN data for bayes calculations, and thus
@ -120,7 +204,7 @@ been performed.
=head1 SEE ALSO =head1 SEE ALSO
http://www.routeviews.org/ - all data regarding routing, ASNs, etc.... https://www.routeviews.org/ - all data regarding routing, ASNs, etc....
=cut =cut
@ -159,82 +243,9 @@ sub set_config {
my ($self, $conf) = @_; my ($self, $conf) = @_;
my @cmds; my @cmds;
=head1 ADMINISTRATOR SETTINGS
=over 4
=item asn_lookup asn-zone.example.com [ _ASNTAG_ _ASNCIDRTAG_ ]
Use this to lookup the ASN info in the specified zone for the first external
IPv4 address and add the AS number to the first specified tag and routing info
to the second specified tag.
If no tags are specified the AS number will be added to the _ASN_ tag and the
routing info will be added to the _ASNCIDR_ tag. You must specify either none
or both of the tag names. Tag names must start and end with an underscore.
If two or more I<asn_lookup>s use the same set of template tags, the results of
their lookups will be appended to each other in the template tag values in no
particular order. Duplicate results will be omitted when combining results.
In a similar fashion, you can also use the same template tag for both the AS
number tag and the routing info tag.
Examples:
asn_lookup asn.routeviews.org
asn_lookup asn.routeviews.org _ASN_ _ASNCIDR_
asn_lookup myview.example.com _MYASN_ _MYASNCIDR_
asn_lookup asn.routeviews.org _COMBINEDASN_ _COMBINEDASNCIDR_
asn_lookup myview.example.com _COMBINEDASN_ _COMBINEDASNCIDR_
asn_lookup in1tag.example.net _ASNDATA_ _ASNDATA_
=item asn_lookup_ipv6 asn-zone6.example.com [_ASN_ _ASNCIDR_]
Use specified zone for lookups of IPv6 addresses. If zone supports both
IPv4 and IPv6 queries, use both asn_lookup and asn_lookup_ipv6 for the same
zone.
=item clear_asn_lookups
Removes any previously declared I<asn_lookup> entries from a list of queries.
=item asn_prefix 'prefix_string' (default: 'AS')
The string specified in the argument is prepended to each ASN when storing
it as a tag. This prefix is rather redundant, but its default value 'AS'
is kept for backward compatibility with versions of SpamAssassin earlier
than 3.4.0. A sensible setting is an empty string. The argument may be (but
need not be) enclosed in single or double quotes for clarity.
=item asn_use_geodb ( 0 / 1 ) (default: 1)
Use Mail::SpamAssassin::GeoDB module to lookup ASN numbers. You need
suitable supported module like GeoIP2 or GeoIP with ISP or ASN database
installed (for example, add EditionIDs GeoLite2-ASN in GeoIP.conf for
geoipupdate program).
GeoDB can only set _ASN_ tag, it has no data for _ASNCIDR_. If you need
both, then set asn_prefer_geodb 0 so DNS rules are tried.
=item asn_prefer_geodb ( 0 / 1 ) (default: 1)
If set, DNS lookups (asn_lookup rules) will not be run if GeoDB successfully
finds ASN. Set this to 0 to get _ASNCIDR_ even if GeoDB finds _ASN_.
=item asn_use_dns ( 0 / 1 ) (default: 1)
Set to 0 to never allow DNS queries.
=back
=cut
push (@cmds, { push (@cmds, {
setting => 'asn_lookup', setting => 'asn_lookup',
is_admin => 1, is_priv => 1,
code => sub { code => sub {
my ($conf, $key, $value, $line) = @_; my ($conf, $key, $value, $line) = @_;
unless (defined $value && $value !~ /^$/) { unless (defined $value && $value !~ /^$/) {
@ -254,7 +265,7 @@ Set to 0 to never allow DNS queries.
push (@cmds, { push (@cmds, {
setting => 'asn_lookup_ipv6', setting => 'asn_lookup_ipv6',
is_admin => 1, is_priv => 1,
code => sub { code => sub {
my ($conf, $key, $value, $line) = @_; my ($conf, $key, $value, $line) = @_;
unless (defined $value && $value !~ /^$/) { unless (defined $value && $value !~ /^$/) {
@ -274,7 +285,6 @@ Set to 0 to never allow DNS queries.
push (@cmds, { push (@cmds, {
setting => 'clear_asn_lookups', setting => 'clear_asn_lookups',
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NOARGS, type => $Mail::SpamAssassin::Conf::CONF_TYPE_NOARGS,
code => sub { code => sub {
my ($conf, $key, $value, $line) = @_; my ($conf, $key, $value, $line) = @_;

View File

@ -33,21 +33,7 @@ responses trickle in, filters them according to the requested DNS resource
record type and optional subrule filtering expression, yielding a rule hit record type and optional subrule filtering expression, yielding a rule hit
if a response meets filtering conditions. if a response meets filtering conditions.
=head1 USER SETTINGS =head1 RULE DEFINITIONS AND PRIVILEGED SETTINGS
=over 4
=item rbl_timeout t [t_min] [zone] (default: 15 3)
The rbl_timeout setting is common to all DNS querying rules (as implemented
by other plugins). It can specify a DNS query timeout globally, or individually
for each zone. When the zone parameter is specified, the settings affects DNS
queries when their query domain equals the specified zone, or is its subdomain.
See the C<Mail::SpamAssassin::Conf> POD for details on C<rbl_timeout>.
=back
=head1 RULE DEFINITIONS
=over 4 =over 4
@ -173,7 +159,7 @@ delimited by a '-' specifies an IPv4 address range, and a pair of values
delimited by a '/' specifies an IPv4 address followed by a bitmask. Again, delimited by a '/' specifies an IPv4 address followed by a bitmask. Again,
this type of filtering expression is primarily intended with RR type-A this type of filtering expression is primarily intended with RR type-A
DNS queries. The rule hits if the RR type matches, and the returned IP DNS queries. The rule hits if the RR type matches, and the returned IP
address falls within the specified range: (r >= n1 && r <= n2), or address falls within the specified range: (r E<gt>= n1 && r E<lt>= n2), or
masked with a bitmask matches the specified value: (r & m) == (n & m) . masked with a bitmask matches the specified value: (r & m) == (n & m) .
As a shorthand notation, a single quad-dotted value is equivalent to As a shorthand notation, a single quad-dotted value is equivalent to
@ -193,6 +179,11 @@ rcode indicates an error. Example: [NXDOMAIN], or [FormErr,ServFail,4,5] .
=back =back
=head1 NOTES
DNS timeout can be set with C<rbl_timeout> option. See the
C<Mail::SpamAssassin::Conf> POD for details on C<rbl_timeout>.
=cut =cut
package Mail::SpamAssassin::Plugin::AskDNS; package Mail::SpamAssassin::Plugin::AskDNS;
@ -309,7 +300,7 @@ sub set_config {
push(@cmds, { push(@cmds, {
setting => 'askdns', setting => 'askdns',
is_admin => 1, is_priv => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE, type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
code => sub { code => sub {
my($self, $key, $value, $line) = @_; my($self, $key, $value, $line) = @_;

View File

@ -51,12 +51,15 @@ our @ISA = qw(Mail::SpamAssassin::Plugin);
# https://www.iana.org/assignments/email-auth/email-auth.xhtml # https://www.iana.org/assignments/email-auth/email-auth.xhtml
# some others not in that list: # some others not in that list:
# dkim-atps=neutral # dkim-atps=neutral
# dmarc=bestguesspass (some microsoft stuff)
my %method_result = ( my %method_result = (
'arc' => {'fail'=>1,'none'=>1,'pass'=>1},
'auth' => {'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1}, 'auth' => {'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1},
'dkim' => {'fail'=>1,'neutral'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'policy'=>1,'temperror'=>1}, 'dkim' => {'fail'=>1,'neutral'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'policy'=>1,'temperror'=>1},
'dkim-adsp' => {'discard'=>1,'fail'=>1,'none'=>1,'nxdomain'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1,'unknown'=>1}, 'dkim-adsp' => {'discard'=>1,'fail'=>1,'none'=>1,'nxdomain'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1,'unknown'=>1},
'dkim-atps' => {'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1,'neutral'=>1}, 'dkim-atps' => {'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1,'neutral'=>1},
'dmarc' => {'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1}, 'dmarc' => {'bestguesspass'=>1,'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1},
'dnswl' => {'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1},
'domainkeys' => {'fail'=>1,'neutral'=>1,'none'=>1,'permerror'=>1,'policy'=>1,'pass'=>1,'temperror'=>1}, 'domainkeys' => {'fail'=>1,'neutral'=>1,'none'=>1,'permerror'=>1,'policy'=>1,'pass'=>1,'temperror'=>1},
'iprev' => {'fail'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1}, 'iprev' => {'fail'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1},
'rrvs' => {'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1,'unknown'=>1}, 'rrvs' => {'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1,'unknown'=>1},
@ -66,17 +69,19 @@ my %method_result = (
'vbr' => {'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1}, 'vbr' => {'fail'=>1,'none'=>1,'pass'=>1,'permerror'=>1,'temperror'=>1},
); );
my %method_ptype_prop = ( my %method_ptype_prop = (
'arc' => {'smtp' => {'remote-ip'=>1}, 'header' => {'oldest-pass'=>1}, 'arc' => {'chain'=>1}},
'auth' => {'smtp' => {'auth'=>1,'mailfrom'=>1}}, 'auth' => {'smtp' => {'auth'=>1,'mailfrom'=>1}},
'dkim' => {'header' => {'d'=>1,'i'=>1,'b'=>1}}, 'dkim' => {'header' => {'d'=>1,'i'=>1,'b'=>1,'a'=>1,'s'=>1}},
'dkim-adsp' => {'header' => {'from'=>1}}, 'dkim-adsp' => {'header' => {'from'=>1}},
'dkim-atps' => {'header' => {'from'=>1}}, 'dkim-atps' => {'header' => {'from'=>1}},
'dmarc' => {'header' => {'from'=>1}}, 'dmarc' => {'header' => {'from'=>1}, 'policy' => {'dmarc'=>1}},
'dnswl' => {'dns' => {'zone'=>1,'sec'=>1}, 'policy' => {'ip'=>1,'txt'=>1}},
'domainkeys' => {'header' => {'d'=>1,'from'=>1,'sender'=>1}}, 'domainkeys' => {'header' => {'d'=>1,'from'=>1,'sender'=>1}},
'iprev' => {'policy' => {'iprev'=>1}}, 'iprev' => {'policy' => {'iprev'=>1}},
'rrvs' => {'smtp' => {'rcptto'=>1}}, 'rrvs' => {'smtp' => {'rcptto'=>1}},
'sender-id' => {'header' => {'*'=>1}}, 'sender-id' => {'header' => {'*'=>1}},
'smime' => {'body' => {'smime-part'=>1,'smime-identifer'=>1,'smime-serial'=>1,'smime-issuer'=>1}}, 'smime' => {'body' => {'smime-part'=>1,'smime-identifer'=>1,'smime-serial'=>1,'smime-issuer'=>1}},
'spf' => {'smtp' => {'mailfrom'=>1,'helo'=>1}}, 'spf' => {'smtp' => {'mailfrom'=>1,'mfrom'=>1,'helo'=>1,'rcpttodomain'=>1}},
'vbr' => {'header' => {'md'=>1,'mv'=>1}}, 'vbr' => {'header' => {'md'=>1,'mv'=>1}},
); );
@ -107,7 +112,7 @@ sub set_config {
my ($self, $conf) = @_; my ($self, $conf) = @_;
my @cmds; my @cmds;
=head1 ADMINISTRATOR OPTIONS =head1 ADMINISTRATOR SETTINGS
=over =over
@ -272,6 +277,12 @@ For checking result of methods, $pms-E<gt>{authres_result} is available:
Can be used to check results. Can be used to check results.
Reference of valid result methods and values:
C<https://www.iana.org/assignments/email-auth/email-auth.xhtml>
Additionally the result value of 'missing' can be used to check if there is
no result at all.
ifplugin Mail::SpamAssassin::Plugin::AuthRes ifplugin Mail::SpamAssassin::Plugin::AuthRes
ifplugin !(Mail::SpamAssassin::Plugin::SPF) ifplugin !(Mail::SpamAssassin::Plugin::SPF)
header SPF_PASS eval:check_authres_result('spf', 'pass') header SPF_PASS eval:check_authres_result('spf', 'pass')
@ -299,7 +310,8 @@ sub check_authres_result {
return !defined($result) ? 1 : 0; return !defined($result) ? 1 : 0;
} }
return ($wanted_result eq $result); return 0 unless defined $result;
return ($wanted_result eq $result) ? 1 : 0;
} }
sub parsed_metadata { sub parsed_metadata {
@ -319,8 +331,8 @@ sub parsed_metadata {
} }
foreach my $hdr (split(/^/m, $pms->get($nethdr))) { foreach my $hdr (split(/^/m, $pms->get($nethdr))) {
if ($hdr =~ /^(?:Arc\-)?Authentication-Results:\s*(.+)/i) { if ($hdr =~ /^((?:Arc\-)?Authentication-Results):\s*(.+)/i) {
push @authres, $1; push @authres, [$1,$2];
} }
} }
@ -332,7 +344,7 @@ sub parsed_metadata {
foreach (@authres) { foreach (@authres) {
eval { eval {
$self->parse_authres($pms, $_); $self->parse_authres($pms, $_->[0], $_->[1]);
} or do { } or do {
dbg("authres: skipping header, $@"); dbg("authres: skipping header, $@");
} }
@ -363,22 +375,51 @@ sub parsed_metadata {
} }
sub parse_authres { sub parse_authres {
my ($self, $pms, $hdr) = @_; my ($self, $pms, $hdrname, $hdr) = @_;
dbg("authres: parsing Authentication-Results: $hdr"); dbg("authres: parsing $hdrname: $hdr");
my $authserv; my $authserv;
my $version = 1; my $version = 1;
my @methods; my @methods;
my $arc_index;
local $_ = $hdr; local $_ = $hdr;
if ($hdrname =~ /^ARC-/i) {
if (!/\Gi\b/gcs) {
die("missing arc index: $hdr");
}
skip_cfws();
if (!/\G=/gcs) {
die("invalid arc index: ".substr($_, pos())."\n");
}
skip_cfws();
if (!/\G(\d+)/gcs) {
die("invalid arc index: ".substr($_, pos())."\n");
}
$arc_index = $1;
if ($arc_index < 1 || $arc_index > 50) {
die("invalid arc index: $arc_index\n");
}
skip_cfws();
if (!/\G;/gcs) {
die("missing delimiter: ".substr($_, pos())."\n");
}
skip_cfws();
}
# authserv-id # authserv-id
if (!/\G($TOKEN)/gcs) { if (!/\G($TOKEN)/gcs) {
die("invalid authserv\n"); die("invalid authserv: ".substr($_, pos())."\n");
} }
$authserv = lc($1); $authserv = lc($1);
# some invalid headers start with spf=foo etc, missing authserv-id
if (/\G=/gcs) {
die("missing authserv: $hdr\n");
}
if (%{$pms->{conf}->{authres_trusted_authserv}}) { if (%{$pms->{conf}->{authres_trusted_authserv}}) {
if (!$pms->{conf}->{authres_trusted_authserv}->{$authserv}) { if (!$pms->{conf}->{authres_trusted_authserv}->{$authserv}) {
die("authserv not trusted: $authserv\n"); die("authserv not trusted: $authserv\n");
@ -388,12 +429,14 @@ sub parse_authres {
die("ignored authserv: $authserv\n"); die("ignored authserv: $authserv\n");
} }
# skip authserv version
skip_cfws(); skip_cfws();
if (/\G\d+/gcs) { # skip authserv version if (/\G\d+/gcs) {
skip_cfws(); skip_cfws();
} }
if (!/\G;/gcs) { if (!/\G;/gcs) {
die("missing delimiter\n"); die("missing delimiter: ".substr($_, pos())."\n");
} }
skip_cfws(); skip_cfws();
@ -402,6 +445,11 @@ sub parse_authres {
my $reason = ''; my $reason = '';
my $props = {}; my $props = {};
# some silly generators add duplicate authserv-id; here
if (/\G\Q${authserv}\E\s*;/gcs) {
skip_cfws();
}
# skip none method # skip none method
if (/\Gnone\b/igcs) { if (/\Gnone\b/igcs) {
die("method none\n"); die("method none\n");
@ -409,17 +457,17 @@ sub parse_authres {
# method / version = result # method / version = result
if (!/\G([\w-]+)/gcs) { if (!/\G([\w-]+)/gcs) {
die("invalid method\n"); die("invalid method: ".substr($_, pos())."\n");
} }
$method = lc($1); $method = lc($1);
if (!exists $method_result{$method}) { if (!exists $method_result{$method}) {
die("unknown method: $method\n"); die("unknown method: $method: $hdr\n");
} }
skip_cfws(); skip_cfws();
if (/\G\//gcs) { if (/\G\//gcs) {
skip_cfws(); skip_cfws();
if (!/\G\d+/gcs) { if (!/\G\d+/gcs) {
die("invalid $method version\n"); die("invalid $method version: ".substr($_, pos())."\n");
} }
$version = $1; $version = $1;
skip_cfws(); skip_cfws();
@ -429,7 +477,7 @@ sub parse_authres {
} }
skip_cfws(); skip_cfws();
if (!/\G(\w+)/gcs) { if (!/\G(\w+)/gcs) {
die("invalid result for $method\n"); die("invalid result for $method: ".substr($_, pos())."\n");
} }
$result = $1; $result = $1;
if (!exists $method_result{$method}{$result}) { if (!exists $method_result{$method}{$result}) {
@ -441,50 +489,69 @@ sub parse_authres {
if (/\Greason\b/igcs) { if (/\Greason\b/igcs) {
skip_cfws(); skip_cfws();
if (!/\G=/gcs) { if (!/\G=/gcs) {
die("invalid reason\n"); die("invalid reason: ".substr($_, pos())."\n");
} }
skip_cfws(); skip_cfws();
if (!/\G$QUOTED_STRING|($TOKEN)/gcs) { if (!/\G$QUOTED_STRING|($TOKEN)/gcs) {
die("invalid reason\n"); die("invalid reason: ".substr($_, pos())."\n");
} }
$reason = defined $1 ? $1 : $2; $reason = defined $1 ? $1 : $2;
skip_cfws(); skip_cfws();
} }
# action = value (some microsoft ARC stuff?)
if (/\Gaction\b/igcs) {
skip_cfws();
if (!/\G=/gcs) {
die("invalid action: ".substr($_, pos())."\n");
}
skip_cfws();
if (!/\G$QUOTED_STRING|$TOKEN/gcs) {
die("invalid action: ".substr($_, pos())."\n");
}
skip_cfws();
}
# ptype.property = value # ptype.property = value
while (pos() < length()) { while (pos() < length()) {
my ($ptype, $property, $value); my ($ptype, $property, $value);
# no props?
if (/\G(?:;|$)/gcs) {
skip_cfws();
last;
}
# ptype # ptype
if (!/\G(\w+)/gcs) { if (!/\G([\w-]+)/gcs) {
die("invalid ptype: ".substr($_,pos())."\n"); die("invalid ptype: ".substr($_,pos())."\n");
} }
$ptype = lc($1); $ptype = lc($1);
if (!exists $method_ptype_prop{$method}{$ptype}) { if (!exists $method_ptype_prop{$method}{$ptype}) {
die("unknown ptype: $ptype\n"); die("unknown ptype: $method/$ptype\n");
} }
skip_cfws(); skip_cfws();
# dot # dot
if (!/\G\./gcs) { if (!/\G\./gcs) {
die("missing property\n"); die("missing property: ".substr($_, pos())."\n");
} }
skip_cfws(); skip_cfws();
# property # property
if (!/\G(\w+)/gcs) { if (!/\G([\w-]+)/gcs) {
die("invalid property\n"); die("invalid property: ".substr($_, pos())."\n");
} }
$property = lc($1); $property = lc($1);
if (!exists $method_ptype_prop{$method}{$ptype}{$property} && if (!exists $method_ptype_prop{$method}{$ptype}{$property} &&
!exists $method_ptype_prop{$method}{$ptype}{'*'}) { !exists $method_ptype_prop{$method}{$ptype}{'*'}) {
die("unknown property for $ptype: $property\n"); die("unknown property for $method/$ptype: $property\n");
} }
skip_cfws(); skip_cfws();
# = # =
if (!/\G=/gcs) { if (!/\G=/gcs) {
die("missing property value\n"); die("missing property value: ".substr($_, pos())."\n");
} }
skip_cfws(); skip_cfws();
@ -493,7 +560,7 @@ sub parse_authres {
# where value := token / quoted-string # where value := token / quoted-string
# and local-part := dot-atom / quoted-string / obs-local-part # and local-part := dot-atom / quoted-string / obs-local-part
if (!/\G$QUOTED_STRING|($ATOM(?:\.$ATOM)*|$TOKEN)(?=(?:[\s;]|$))/gcs) { if (!/\G$QUOTED_STRING|($ATOM(?:\.$ATOM)*|$TOKEN)(?=(?:[\s;]|$))/gcs) {
die("invalid $ptype.$property value\n"); die("invalid $method/$ptype.$property value: ".substr($_, pos())."\n");
} }
$value = defined $1 ? $1 : $2; $value = defined $1 ? $1 : $2;
skip_cfws(); skip_cfws();
@ -512,12 +579,13 @@ sub parse_authres {
'result' => $result, 'result' => $result,
'reason' => $reason, 'reason' => $reason,
'properties' => $props, 'properties' => $props,
'arc_index' => $arc_index,
}]; }];
} }
# paranoid check.. # paranoid check..
if (pos() < length()) { if (pos() < length()) {
die("parse ended prematurely?\n"); die("parse ended prematurely? ".substr($_, pos())."\n");
} }
# Pushed to pms only if header parsed completely # Pushed to pms only if header parsed completely

View File

@ -77,7 +77,7 @@ sub set_config {
my($self, $conf) = @_; my($self, $conf) = @_;
my @cmds; my @cmds;
=head1 USER OPTIONS =head1 USER SETTINGS
The following configuration settings are used to control auto-learning: The following configuration settings are used to control auto-learning:

View File

@ -37,7 +37,7 @@ And the chi-square probability combiner as described here:
The results are incorporated into SpamAssassin as the BAYES_* rules. The results are incorporated into SpamAssassin as the BAYES_* rules.
=head1 ADMINISTRATOR SETTINGS =head1 USER SETTINGS
=over 4 =over 4
@ -288,14 +288,12 @@ sub set_config {
push(@cmds, { push(@cmds, {
setting => 'bayes_max_token_length', setting => 'bayes_max_token_length',
default => MAX_TOKEN_LENGTH, default => MAX_TOKEN_LENGTH,
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
}); });
push(@cmds, { push(@cmds, {
setting => 'bayes_stopword_languages', setting => 'bayes_stopword_languages',
default => ['en'], default => ['en'],
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRINGLIST, type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRINGLIST,
code => sub { code => sub {
my ($self, $key, $value, $line) = @_; my ($self, $key, $value, $line) = @_;
@ -324,9 +322,6 @@ sub set_config {
sub parse_config { sub parse_config {
my ($self, $opts) = @_; my ($self, $opts) = @_;
# Ignore users's configuration lines
return 0 if $opts->{user_config};
if ($opts->{key} =~ /^bayes_stopword_([a-z]{2})$/i) { if ($opts->{key} =~ /^bayes_stopword_([a-z]{2})$/i) {
$self->inhibit_further_callbacks(); $self->inhibit_further_callbacks();
my $lang = lc($1); my $lang = lc($1);
@ -1790,7 +1785,10 @@ sub learner_new {
} }
dbg("bayes: learner_new self=%s, bayes_store_module=%s", $self,$module); dbg("bayes: learner_new self=%s, bayes_store_module=%s", $self,$module);
undef $self->{store}; # DESTROYs previous object, if any if ($self->{store}) {
$self->{store}->untie_db();
undef $self->{store}; # DESTROYs previous object, if any
}
eval ' eval '
require '.$module.'; require '.$module.';
$store = '.$module.'->new($self); $store = '.$module.'->new($self);

View File

@ -666,11 +666,13 @@ sub do_head_tests {
# setup the function to run the rules # setup the function to run the rules
while(my($k,$v) = each %ordered) { while(my($k,$v) = each %ordered) {
my($hdrname, $def) = split(/\t/, $k, 2); my($hdrname, $def) = split(/\t/, $k, 2);
# get() might already include newlines, join accordingly (Bug 8121)
$self->push_evalstr_prefix($pms, ' $self->push_evalstr_prefix($pms, '
@harr = $self->get(q{'.$hdrname.'}); if (scalar(@harr = $self->get(q{'.$hdrname.'}))) {
$hval = scalar(@harr) ? join("\n", @harr) : ' . $hval = join($harr[0] =~ /\n\z/ ? "" : "\n", @harr);
(!defined($def) ? 'undef' : } else {
'$self->{conf}->{test_opt_unset}->{q{'.$def.'}}') . '; $hval = '.(!defined($def) ? 'undef' :'$self->{conf}->{test_opt_unset}->{q{'.$def.'}}').'
}
'); ');
foreach my $rulename (@{$v}) { foreach my $rulename (@{$v}) {
my $tc_ref = $testcode{$rulename}; my $tc_ref = $testcode{$rulename};

View File

@ -128,7 +128,7 @@ sub set_config {
my($self, $conf) = @_; my($self, $conf) = @_;
my @cmds; my @cmds;
=head1 USER OPTIONS =head1 USER SETTINGS
=over 4 =over 4
@ -141,7 +141,6 @@ Whether to use DCC, if it is available.
push(@cmds, { push(@cmds, {
setting => 'use_dcc', setting => 'use_dcc',
default => 1, default => 1,
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL, type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
}); });
@ -156,7 +155,6 @@ where it's automatically used.
push(@cmds, { push(@cmds, {
setting => 'use_dcc_rep', setting => 'use_dcc_rep',
default => 1, default => 1,
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL, type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
}); });
@ -189,32 +187,28 @@ default is C<90>.
push (@cmds, { push (@cmds, {
setting => 'dcc_body_max', setting => 'dcc_body_max',
is_admin => 1,
default => 999999, default => 999999,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
}, },
{ {
setting => 'dcc_fuz1_max', setting => 'dcc_fuz1_max',
is_admin => 1,
default => 999999, default => 999999,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
}, },
{ {
setting => 'dcc_fuz2_max', setting => 'dcc_fuz2_max',
is_admin => 1,
default => 999999, default => 999999,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
}, },
{ {
setting => 'dcc_rep_percent', setting => 'dcc_rep_percent',
is_admin => 1,
default => 90, default => 90,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
}); });
=back =back
=head1 ADMINISTRATOR OPTIONS =head1 ADMINISTRATOR SETTINGS
=over 4 =over 4
@ -799,7 +793,7 @@ sub _launch_dcc {
return; return;
} }
if ($pms->get('ALL-TRUSTED') =~ /^(X-DCC-[^:]*?-Metrics: .*)$/m) { #if ($pms->get('ALL-TRUSTED') =~ /^(X-DCC-[^:]*?-Metrics: .*)$/m) {
# short-circuit if there is already a X-DCC header with value of # short-circuit if there is already a X-DCC header with value of
# "bulk" from an upstream DCC check # "bulk" from an upstream DCC check
# require "bulk" because then at least one body checksum will be "many" # require "bulk" because then at least one body checksum will be "many"
@ -807,7 +801,7 @@ sub _launch_dcc {
#if ($1 =~ / bulk /) { #if ($1 =~ / bulk /) {
# return $self->check_dcc_result($pms, $1); # return $self->check_dcc_result($pms, $1);
#} #}
} #}
my $envelope = $pms->{relays_external}->[0]; my $envelope = $pms->{relays_external}->[0];

View File

@ -134,6 +134,7 @@ use Mail::SpamAssassin::Plugin;
use Mail::SpamAssassin::Logger; use Mail::SpamAssassin::Logger;
use Mail::SpamAssassin::Timeout; use Mail::SpamAssassin::Timeout;
use Mail::SpamAssassin::Util qw(idn_to_ascii); use Mail::SpamAssassin::Util qw(idn_to_ascii);
use version;
use strict; use strict;
use warnings; use warnings;
@ -730,7 +731,6 @@ sub _dkim_load_modules {
dbg("dkim: cannot load Mail::DKIM module, DKIM checks disabled: %s", dbg("dkim: cannot load Mail::DKIM module, DKIM checks disabled: %s",
$eval_stat); $eval_stat);
} else { } else {
use version 0.77;
my $version = Mail::DKIM::Verifier->VERSION; my $version = Mail::DKIM::Verifier->VERSION;
if (version->parse($version) >= version->parse(0.31)) { if (version->parse($version) >= version->parse(0.31)) {
dbg("dkim: using Mail::DKIM version $version"); dbg("dkim: using Mail::DKIM version $version");
@ -807,7 +807,7 @@ sub _check_dkim_signed_by {
} }
sub _get_authors { sub _get_authors {
my ($self, $pms) = @_; my ($self, $pms, $sig_type) = @_;
# Note that RFC 5322 permits multiple addresses in the From header field, # Note that RFC 5322 permits multiple addresses in the From header field,
# and according to RFC 5617 such message has multiple authors and hence # and according to RFC 5617 such message has multiple authors and hence
@ -820,8 +820,8 @@ sub _get_authors {
# be tolerant, ignore trailing WSP after a domain name # be tolerant, ignore trailing WSP after a domain name
$author_domains{lc $1} = 1 if /\@([^\@]+?)[ \t]*\z/s; $author_domains{lc $1} = 1 if /\@([^\@]+?)[ \t]*\z/s;
} }
$pms->{dkim_author_addresses} = \@authors; # list of full addresses $pms->{"${sig_type}_author_addresses"} = \@authors; # list of full addresses
$pms->{dkim_author_domains} = \%author_domains; # hash of their domains $pms->{"${sig_type}_author_domains"} = \%author_domains; # hash of their domains
} }
sub _check_dkim_signature { sub _check_dkim_signature {
@ -883,7 +883,7 @@ sub _check_dkim_signature {
} else { } else {
# signature objects not provided by the caller, must verify for ourselves # signature objects not provided by the caller, must verify for ourselves
my $timemethod = $self->{main}->time_method("check_dkim_signature"); my $timemethod = $self->{main}->time_method("check_dkim_signature");
if (Mail::DKIM::Verifier->VERSION >= 0.40) { if (version->parse(Mail::DKIM::Verifier->VERSION) >= version->parse(0.40)) {
my $edns = $conf->{dns_options}->{edns}; my $edns = $conf->{dns_options}->{edns};
if ($edns && $edns >= 1024) { if ($edns && $edns >= 1024) {
# Let Mail::DKIM use our interface to Net::DNS::Resolver. # Let Mail::DKIM use our interface to Net::DNS::Resolver.
@ -908,7 +908,7 @@ sub _check_signature {
my($self, $pms, $verifier, $type, $signatures) = @_; my($self, $pms, $verifier, $type, $signatures) = @_;
my $sig_type = lc $type; my $sig_type = lc $type;
$self->_get_authors($pms) if !$pms->{"${sig_type}_author_addresses"}; $self->_get_authors($pms, $sig_type) if !$pms->{"${sig_type}_author_addresses"};
my(@valid_signatures); my(@valid_signatures);
my $conf = $pms->{conf}; my $conf = $pms->{conf};
@ -1003,7 +1003,7 @@ sub _check_valid_signature {
my($self, $pms, $verifier, $type, $signatures) = @_; my($self, $pms, $verifier, $type, $signatures) = @_;
my $sig_type = lc $type; my $sig_type = lc $type;
$self->_get_authors($pms) if !$pms->{"${sig_type}_author_addresses"}; $self->_get_authors($pms, $sig_type) if !$pms->{"${sig_type}_author_addresses"};
my(@valid_signatures); my(@valid_signatures);
my $conf = $pms->{conf}; my $conf = $pms->{conf};
@ -1143,7 +1143,7 @@ sub _check_dkim_adsp {
$pms->{dkim_adsp} = {}; # a hash: author_domain => adsp $pms->{dkim_adsp} = {}; # a hash: author_domain => adsp
my $practices_as_string = ''; my $practices_as_string = '';
$self->_get_authors($pms) if !$pms->{dkim_author_addresses}; $self->_get_authors($pms, 'dkim') if !$pms->{dkim_author_addresses};
# collect only fully qualified domain names, allow '-', think of IDN # collect only fully qualified domain names, allow '-', think of IDN
my @author_domains = grep { /.\.[a-z-]{2,}\z/si } my @author_domains = grep { /.\.[a-z-]{2,}\z/si }
@ -1296,7 +1296,7 @@ sub _check_dkim_welcomelist {
$pms->{welcomelist_checked} = 1; $pms->{welcomelist_checked} = 1;
$self->_get_authors($pms) if !$pms->{dkim_author_addresses}; $self->_get_authors($pms, 'dkim') if !$pms->{dkim_author_addresses};
my $authors_str = join(", ", @{$pms->{dkim_author_addresses}}); my $authors_str = join(", ", @{$pms->{dkim_author_addresses}});
if ($authors_str eq '') { if ($authors_str eq '') {

View File

@ -108,6 +108,7 @@ Store DMARC reports using Mail::DMARC::Store, mail-dmarc.ini must be configured
push(@cmds, { push(@cmds, {
setting => 'dmarc_save_reports', setting => 'dmarc_save_reports',
is_admin => 1,
default => 0, default => 0,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL, type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
}); });
@ -326,6 +327,80 @@ sub _check_dmarc {
return; return;
} }
my $dmarc_arc_verified = 0;
if (($result->result ne 'pass') and (ref($pms->{arc_verifier}) and ($pms->{arc_verifier}->result))) {
undef $result;
$dmarc_arc_verified = 1;
# if DMARC fails retry by reading data from AAR headers
# use Mail::SpamAssassin::Plugin::AuthRes if available to read ARC signature details
my @spf_parsed = sort { ( $a->{authres_parsed}{spf}{arc_index} // 0 ) <=> ( $b->{authres_parsed}{spf}{arc_index} // 0 ) } @{$pms->{authres_parsed}{spf}};
my $old_arc_index = 0;
foreach my $spf_parse ( @spf_parsed ) {
last if not defined $spf_parse->{arc_index};
last if $old_arc_index > $spf_parse->{arc_index};
dbg("Evaluate DMARC using AAR spf information for index $spf_parse->{arc_index}");
if(exists $spf_parse->{properties}{smtp}{mailfrom}) {
my $mfrom_dom = $spf_parse->{properties}{smtp}{mailfrom};
if($mfrom_dom =~ /\@(.*)/) {
$mfrom_dom = $1;
}
$dmarc->spf([
{
scope => 'mfrom',
domain => $mfrom_dom,
result => $spf_parse->{result},
}
]);
}
if(exists $spf_parse->{properties}{smtp}{helo}) {
$dmarc->spf([
{
scope => 'helo',
domain => $spf_parse->{properties}{smtp}{helo},
result => $spf_parse->{result},
}
]);
}
$old_arc_index = $spf_parse->{arc_index};
}
my @tmp_arc_seals;
my @arc_seals;
if(defined $pms->{arc_verifier}{seals}) {
@tmp_arc_seals = @{$pms->{arc_verifier}{seals}};
@arc_seals = sort { ( $a->{arc_verifier}{seals}{tags_by_name}{i}{value} // 0 ) <=> ( $b->{arc_verifier}{seals}{tags_by_name}{i}{value} // 0 ) } @tmp_arc_seals;
foreach my $seals ( @arc_seals ) {
if(exists($seals->{tags_by_name}{d}) and exists($pms->{arc_author_domains}->{$mfrom_domain})) {
dbg("Evaluate DMARC using AAR dkim information for index $seals->{tags_by_name}{i}{value} on domain $mfrom_domain and selector $seals->{tags_by_name}{s}{value}. Result is $seals->{verify_result}");
my $arc_result = $seals->{verify_result};
if($seals->{verify_result} eq 'invalid') {
$arc_result = 'permerror';
}
$dmarc->dkim(domain => $mfrom_domain, selector => $seals->{tags_by_name}{s}{value}, result => $arc_result);
last;
}
}
}
eval { $result = $dmarc->validate(); };
}
# Report that DMARC failed but it has been overridden because of AAR headers
if(ref($pms->{arc_verifier}) and ($pms->{arc_verifier}->result) and ($dmarc_arc_verified)) {
$result->reason->[0]{type} = 'local_policy';
$result->reason->[0]{comment} = "arc=" . $pms->{arc_verifier}->result;
my $cnt = 1;
foreach my $seals ( @{$pms->{arc_verifier}{seals}} ) {
if(exists($seals->{tags_by_name}{d}) and exists($seals->{tags_by_name}{s})) {
$result->reason->[0]{comment} .= " as[$cnt].d=$seals->{tags_by_name}{d}{value} as[$cnt].s=$seals->{tags_by_name}{s}{value}";
$cnt++;
}
}
if($cnt gt 1) {
$result->reason->[0]{comment} .= " remote-ip[1]=$lasthop->{ip}";
}
}
if (defined($pms->{dmarc_result} = $result->result)) { if (defined($pms->{dmarc_result} = $result->result)) {
if ($pms->{conf}->{dmarc_save_reports}) { if ($pms->{conf}->{dmarc_save_reports}) {
my $rua = eval { $result->published()->rua(); }; my $rua = eval { $result->published()->rua(); };

View File

@ -141,6 +141,7 @@ sub set_config {
my @cmds; my @cmds;
push(@cmds, { push(@cmds, {
setting => 'rbl_headers', setting => 'rbl_headers',
is_priv => 1,
default => 'EnvelopeFrom,Reply-To,Disposition-Notification-To,X-WebmailclientIP,X-Source-IP', default => 'EnvelopeFrom,Reply-To,Disposition-Notification-To,X-WebmailclientIP,X-Source-IP',
type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
} }
@ -516,6 +517,7 @@ sub check_rbl_from_domain {
return $self->_check_rbl_addresses($pms, $rule, $set, $rbl_server, return $self->_check_rbl_addresses($pms, $rule, $set, $rbl_server,
$subtest, $pms->all_from_addrs_domains()); $subtest, $pms->all_from_addrs_domains());
} }
=over 4 =over 4
=item check_rbl_ns_from =item check_rbl_ns_from

View File

@ -91,7 +91,7 @@ use vars qw(@ISA);
my $VERSION = 4.00; my $VERSION = 4.00;
use constant HAS_LWP_USERAGENT => eval { require LWP::UserAgent; }; use constant HAS_LWP_USERAGENT => eval { require LWP::UserAgent; require LWP::Protocol::https; };
sub dbg { my $msg = shift; return Mail::SpamAssassin::Logger::dbg("DecodeShortURLs: $msg", @_); } sub dbg { my $msg = shift; return Mail::SpamAssassin::Logger::dbg("DecodeShortURLs: $msg", @_); }
sub info { my $msg = shift; return Mail::SpamAssassin::Logger::info("DecodeShortURLs: $msg", @_); } sub info { my $msg = shift; return Mail::SpamAssassin::Logger::info("DecodeShortURLs: $msg", @_); }
@ -128,7 +128,7 @@ sub new {
return $self; return $self;
} }
=head1 PRIVILEGED SETTINGS =head1 USER SETTINGS
=over 4 =over 4
@ -218,6 +218,8 @@ then only those are removed from list.
} }
}); });
=head1 PRIVILEGED SETTINGS
=over 4 =over 4
=item url_shortener_cache_type (default: none) =item url_shortener_cache_type (default: none)
@ -329,6 +331,8 @@ See C<url_shortener_cache_autoclean> for database cleaning.
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
}); });
=head1 ADMINISTRATOR SETTINGS
=over 4 =over 4
=item url_shortener_cache_autoclean (default: 1000) =item url_shortener_cache_autoclean (default: 1000)
@ -593,7 +597,7 @@ sub initialise_url_shortener_cache {
"); ");
$self->{sth_delete} = $self->{dbh}->prepare(" $self->{sth_delete} = $self->{dbh}->prepare("
DELETE FROM short_url_cache DELETE FROM short_url_cache
WHERE short_url ? = AND created < CAST(EXTRACT(epoch FROM NOW()) AS INT) - $conf->{url_shortener_cache_ttl} WHERE short_url = ? AND created < CAST(EXTRACT(epoch FROM NOW()) AS INT) - $conf->{url_shortener_cache_ttl}
"); ");
$self->{sth_clean} = $self->{dbh}->prepare(" $self->{sth_clean} = $self->{dbh}->prepare("
DELETE FROM short_url_cache DELETE FROM short_url_cache
@ -707,6 +711,7 @@ sub _check_shortener_uri {
my $levels = $host =~ tr/.//; my $levels = $host =~ tr/.//;
# No point looking at single level "xxx.yy" without a path # No point looking at single level "xxx.yy" without a path
return if $levels == 1 && !$has_path; return if $levels == 1 && !$has_path;
if (exists $conf->{url_shortener}->{$host}) { if (exists $conf->{url_shortener}->{$host}) {
return { return {
'uri' => $uri, 'uri' => $uri,
@ -714,6 +719,18 @@ sub _check_shortener_uri {
}; };
} }
# if domain is a 3rd level domain check if there is a url shortener # if domain is a 3rd level domain check if there is a url shortener
# on the www domain
elsif($levels == 2 && $host =~ /^www\.([^.]+\.[^.]+)$/i) {
my $domain = $1;
if(($host eq "www.$domain") and exists $conf->{url_shortener}->{$domain}) {
dbg("Found internal www redirection for domain $domain");
return {
'uri' => $uri,
'method' => $conf->{url_shortener}->{$domain} == 1 ? 'head' : 'get',
};
}
}
# if domain is a 3rd level domain check if there is a url shortener
# on the 2nd level tld # on the 2nd level tld
elsif ($levels == 2 && $host =~ /^(?!www)[^.]+(\.[^.]+\.[^.]+)$/i && elsif ($levels == 2 && $host =~ /^(?!www)[^.]+(\.[^.]+\.[^.]+)$/i &&
exists $conf->{url_shortener}->{$1}) { exists $conf->{url_shortener}->{$1}) {
@ -742,6 +759,8 @@ sub _check_short {
my $uris = $pms->get_uri_detail_list(); my $uris = $pms->get_uri_detail_list();
while (my($uri, $info) = each %{$uris}) { while (my($uri, $info) = each %{$uris}) {
next unless $info->{domains} && $info->{cleaned}; next unless $info->{domains} && $info->{cleaned};
# Remove anchors and parameters from shortened uris
$uri =~ s/(?:\#|\?).*//g;
if (my $short_url_info = _check_shortener_uri($uri, $conf)) { if (my $short_url_info = _check_shortener_uri($uri, $conf)) {
$short_urls{$uri} = $short_url_info; $short_urls{$uri} = $short_url_info;
last if scalar keys %short_urls >= $conf->{max_short_urls}; last if scalar keys %short_urls >= $conf->{max_short_urls};
@ -827,7 +846,7 @@ sub recursive_lookup {
return; return;
} }
$location = $response->headers->{location}; $location = $response->headers->{location};
if ($self->{url_shortener_loginfo}) { if ($conf->{url_shortener_loginfo}) {
info("found $short_url => $location"); info("found $short_url => $location");
} else { } else {
dbg("found $short_url => $location"); dbg("found $short_url => $location");
@ -850,21 +869,19 @@ sub recursive_lookup {
# redirect back to the same host as chaining incorrectly. # redirect back to the same host as chaining incorrectly.
$pms->{short_url_chained} = 1 if $count; $pms->{short_url_chained} = 1 if $count;
# Check if we are being redirected to a local page # Check if it is a redirection to a relative URI
# Don't recurse in this case... # Make it an absolute URI and chain to it in that case
if ($location !~ m{^[a-z]+://}i) { if ($location !~ m{^[a-z]+://}i) {
my $orig_location = $location; my $orig_location = $location;
my $orig_short_url = $short_url; my $orig_short_url = $short_url;
# Strip to.. # Strip to..
if (index($location, '/') == 0) { if (index($location, '/') == 0) {
$short_url =~ s{^([a-z]+://.*?)[/?#].*}{$1}; # ..absolute path $short_url =~ s{^([a-z]+://.*?)[/?#].*}{$1}; # ..absolute path base is http://example.com
} else { } else {
$short_url =~ s{^([a-z]+://.*)/}{$1}; # ..relative path $short_url =~ s{^([a-z]+://.*/)}{$1}; # ..relative path base is http://example.com/a/b/
} }
$location = "$short_url/$location"; $location = "$short_url$location";
dbg("looks like a local redirection: $orig_short_url => $location ($orig_location)"); dbg("looks like a redirection to a relative URI: $orig_short_url => $location ($orig_location)");
$pms->add_uri_detail_list($location) if !$pms->{uri_detail_list}->{$location};
return;
} }
if (exists $been_here{$location}) { if (exists $been_here{$location}) {

View File

@ -47,6 +47,10 @@ ifplugin Mail::SpamAssassin::Plugin::ExtractText
extracttext_external tesseract {OMP_THREAD_LIMIT=1} /usr/bin/tesseract -c page_separator= {} - extracttext_external tesseract {OMP_THREAD_LIMIT=1} /usr/bin/tesseract -c page_separator= {} -
extracttext_use tesseract .jpg .png .bmp .tif .tiff image/(?:jpeg|png|x-ms-bmp|tiff) extracttext_use tesseract .jpg .png .bmp .tif .tiff image/(?:jpeg|png|x-ms-bmp|tiff)
# QR-code decoder
extracttext_external zbar /usr/bin/zbarimg -q -D {}
extracttext_use zbar .jpg .png .pdf image/(?:jpeg|png) application/pdf
add_header all ExtractText-Flags _EXTRACTTEXTFLAGS_ add_header all ExtractText-Flags _EXTRACTTEXTFLAGS_
header PDF_NO_TEXT X-ExtractText-Flags =~ /\bpdftotext_NoText\b/ header PDF_NO_TEXT X-ExtractText-Flags =~ /\bpdftotext_NoText\b/
describe PDF_NO_TEXT PDF without text describe PDF_NO_TEXT PDF without text
@ -243,6 +247,14 @@ Contains notes from the plugin.
X-ExtractText-Flags: openxml_NoText X-ExtractText-Flags: openxml_NoText
=item X-ExtractText-Uris
Tag: _EXTRACTTEXTURIS_
Contains uris extracted from the plugin.
X-ExtractText-Uris: https://spamassassin.apache.org
=back =back
=head3 Rules =head3 Rules
@ -292,12 +304,14 @@ sub set_config {
push(@cmds, { push(@cmds, {
setting => 'extracttext_maxparts', setting => 'extracttext_maxparts',
is_admin => 1,
default => 10, default => 10,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
}); });
push(@cmds, { push(@cmds, {
setting => 'extracttext_timeout', setting => 'extracttext_timeout',
is_admin => 1,
default => 5, default => 5,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
code => sub { code => sub {
@ -448,7 +462,7 @@ sub _extract_external {
if (proc_status_ok($?, $errno)) { if (proc_status_ok($?, $errno)) {
dbg("extracttext: [%s] (%s) finished successfully", $pid, $cmd[0]); dbg("extracttext: [%s] (%s) finished successfully", $pid, $cmd[0]);
} elsif (proc_status_ok($?, $errno, 0, 1)) { # sometimes it exits with 1 } elsif (proc_status_ok($?, $errno, 0, 1, 4)) { # sometimes it exits with error 1 or 4
dbg("extracttext: [%s] (%s) finished: %s", $pid, $cmd[0], exit_status_str($?, $errno)); dbg("extracttext: [%s] (%s) finished: %s", $pid, $cmd[0], exit_status_str($?, $errno));
} else { } else {
info("extracttext: [%s] (%s) error: %s", $pid, $cmd[0], exit_status_str($?, $errno)); info("extracttext: [%s] (%s) error: %s", $pid, $cmd[0], exit_status_str($?, $errno));
@ -502,6 +516,8 @@ sub _extract_external {
} }
elsif ($err_resp =~ /^\S+ is not a Word Document/) { elsif ($err_resp =~ /^\S+ is not a Word Document/) {
# Ignore antiword # Ignore antiword
} elsif((($pipe_errno/256) eq 4) and ($cmd[0] =~ /zbarimg/)) {
# Ignore zbarimg
} }
elsif (!$resp) { elsif (!$resp) {
warn "extracttext: error (".($pipe_errno/256).") from $cmd[0]: $err_resp\n"; warn "extracttext: error (".($pipe_errno/256).") from $cmd[0]: $err_resp\n";
@ -585,6 +601,14 @@ sub _extract {
push @{$coll->{flags}}, 'ActionURI'; push @{$coll->{flags}}, 'ActionURI';
dbg("extracttext: ActionURI: $1"); dbg("extracttext: ActionURI: $1");
push @{$coll->{text}}, $text; push @{$coll->{text}}, $text;
push @{$coll->{uris}}, $2;
} elsif($text =~ /QR-Code\:([^\s]*)/) {
# zbarimg(1) prefixes the url with "QR-Code:" string
my $qrurl = $1;
push @{$coll->{flags}},'QR-Code';
dbg("extracttext: QR-Code: $qrurl");
push @{$coll->{text}}, $text;
push @{$coll->{uris}}, $qrurl;
} }
if ($text =~ /NoText/) { if ($text =~ /NoText/) {
push @{$coll->{flags}},'NoText'; push @{$coll->{flags}},'NoText';
@ -616,6 +640,7 @@ sub _extract {
# #
sub _check_extract { sub _check_extract {
my ($self, $coll, $checked, $part, $decoded, $data, $type, $name) = @_; my ($self, $coll, $checked, $part, $decoded, $data, $type, $name) = @_;
my $ret = 0;
return 0 unless (defined $type || defined $name); return 0 unless (defined $type || defined $name);
foreach my $match (@{$self->{match}}) { foreach my $match (@{$self->{match}}) {
next unless $self->{tools}->{$match->{tool}}; next unless $self->{tools}->{$match->{tool}};
@ -630,9 +655,11 @@ sub _check_extract {
} }
$checked->{$match->{tool}} = 1; $checked->{$match->{tool}} = 1;
# dbg("extracttext: coll: $coll, part: $part, type: $type, name: $name, data: $data, tool: $self->{tools}->{$match->{tool}}"); # dbg("extracttext: coll: $coll, part: $part, type: $type, name: $name, data: $data, tool: $self->{tools}->{$match->{tool}}");
return 1 if $self->_extract($coll,$part,$type,$name,$data,$self->{tools}->{$match->{tool}}); if($self->_extract($coll,$part,$type,$name,$data,$self->{tools}->{$match->{tool}})) {
$ret = 1;
}
} }
return 0; return $ret;
} }
sub post_message_parse { sub post_message_parse {
@ -652,6 +679,7 @@ sub post_message_parse {
'chars' => 0, 'chars' => 0,
'words' => 0, 'words' => 0,
'text' => [], 'text' => [],
'uris' => [],
); );
my $conf = $self->{main}->{conf}; my $conf = $self->{main}->{conf};
@ -685,6 +713,7 @@ sub post_message_parse {
my @uniq_types = do { my %seen; grep { !$seen{$_}++ } @{$collect{types}} }; my @uniq_types = do { my %seen; grep { !$seen{$_}++ } @{$collect{types}} };
my @uniq_ext = do { my %seen; grep { !$seen{$_}++ } @{$collect{extensions}} }; my @uniq_ext = do { my %seen; grep { !$seen{$_}++ } @{$collect{extensions}} };
my @uniq_flags = do { my %seen; grep { !$seen{$_}++ } @{$collect{flags}} }; my @uniq_flags = do { my %seen; grep { !$seen{$_}++ } @{$collect{flags}} };
my @uniq_uris = do { my %seen; grep { !$seen{$_}++ } @{$collect{uris}} };
$msg->put_metadata('X-ExtractText-Words', $collect{words}); $msg->put_metadata('X-ExtractText-Words', $collect{words});
$msg->put_metadata('X-ExtractText-Chars', $collect{chars}); $msg->put_metadata('X-ExtractText-Chars', $collect{chars});
@ -692,6 +721,7 @@ sub post_message_parse {
$msg->put_metadata('X-ExtractText-Types', join(' ', @uniq_types)); $msg->put_metadata('X-ExtractText-Types', join(' ', @uniq_types));
$msg->put_metadata('X-ExtractText-Extensions', join(' ', @uniq_ext)); $msg->put_metadata('X-ExtractText-Extensions', join(' ', @uniq_ext));
$msg->put_metadata('X-ExtractText-Flags', join(' ', @uniq_flags)); $msg->put_metadata('X-ExtractText-Flags', join(' ', @uniq_flags));
$msg->put_metadata('X-ExtractText-Uris', join(' ', @uniq_uris));
return 1; return 1;
} }
@ -700,7 +730,7 @@ sub parsed_metadata {
my ($self, $opts) = @_; my ($self, $opts) = @_;
my $pms = $opts->{permsgstatus}; my $pms = $opts->{permsgstatus};
my $msg = $pms->get_message(); my $msg = $pms->get_message();
foreach my $tag (('Words','Chars','Tools','Types','Extensions','Flags')) { foreach my $tag (('Words','Chars','Tools','Types','Extensions','Flags','Uris')) {
my $v = $msg->get_metadata("X-ExtractText-$tag"); my $v = $msg->get_metadata("X-ExtractText-$tag");
if (defined $v) { if (defined $v) {
$pms->set_tag("ExtractText$tag", $v); $pms->set_tag("ExtractText$tag", $v);

View File

@ -286,7 +286,6 @@ sub set_config {
push (@cmds, { push (@cmds, {
setting => 'hashbl_ignore', setting => 'hashbl_ignore',
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE, type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
default => {}, default => {},
code => sub { code => sub {
@ -302,7 +301,6 @@ sub set_config {
push (@cmds, { push (@cmds, {
setting => 'hashbl_email_domain_alias', setting => 'hashbl_email_domain_alias',
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE, type => $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE,
default => {}, default => {},
code => sub { code => sub {
@ -323,7 +321,6 @@ sub set_config {
push (@cmds, { push (@cmds, {
setting => 'hashbl_email_regex', setting => 'hashbl_email_regex',
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
# Some regexp tips courtesy of http://www.regular-expressions.info/email.html # Some regexp tips courtesy of http://www.regular-expressions.info/email.html
# full email regex v0.02 # full email regex v0.02
@ -355,7 +352,6 @@ sub set_config {
push (@cmds, { push (@cmds, {
setting => 'hashbl_email_welcomelist', setting => 'hashbl_email_welcomelist',
aliases => ['hashbl_email_whitelist'], # removed in 4.1 aliases => ['hashbl_email_whitelist'], # removed in 4.1
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
default => qr/(?i) default => qr/(?i)
^(?: ^(?:

View File

@ -241,7 +241,7 @@ sub _check_for_forged_hotmail_received_headers {
{ return; } { return; }
if ($rcvd =~ /from \S*\.outbound\.protection\.outlook\.com \(\S+\.outbound\.protection\.outlook\.com[ \)]/ && $orig) if ($rcvd =~ /from \S*\.outbound\.protection\.outlook\.com \(\S+\.outbound\.protection\.outlook\.com[ \)]/ && $orig)
{ return; } { return; }
if ($rcvd =~ /from \S*\.eurprd\d+\.prod\.outlook\.com \($IP_ADDRESS\)/ && $orig) if ($rcvd =~ /from \S*\.(?:eur|nam)prd\d+\.prod\.outlook\.com \($IP_ADDRESS\)/ && $orig)
{ return; } { return; }
if ($rcvd =~ /from \S*\.hotmail.com \(\[$IP_ADDRESS\][ \):]/ && $ip) if ($rcvd =~ /from \S*\.hotmail.com \(\[$IP_ADDRESS\][ \):]/ && $ip)
{ return; } { return; }

View File

@ -81,7 +81,7 @@ use strict;
use warnings; use warnings;
use Mail::SpamAssassin::Plugin; use Mail::SpamAssassin::Plugin;
use Mail::SpamAssassin::Util qw(compile_regexp); use Mail::SpamAssassin::Util qw(compile_regexp get_part_details);
use constant HAS_ARCHIVE_ZIP => eval { require Archive::Zip; }; use constant HAS_ARCHIVE_ZIP => eval { require Archive::Zip; };
use constant HAS_IO_STRING => eval { require IO::String; }; use constant HAS_IO_STRING => eval { require IO::String; };
@ -550,7 +550,7 @@ sub _check_attachments {
foreach my $part ($pms->{msg}->find_parts(qr/./, 1)) { foreach my $part ($pms->{msg}->find_parts(qr/./, 1)) {
next if $part->{type} =~ /$conf->{olemacro_skip_ctypes}/i; next if $part->{type} =~ /$conf->{olemacro_skip_ctypes}/i;
my ($ctt, $ctd, $cte, $name) = _get_part_details($pms, $part); my ($ctt, $ctd, $cte, $name) = get_part_details($pms, $part, $conf->{olemacro_prefer_contentdisposition});
next unless defined $ctt; next unless defined $ctt;
next if $name eq ''; next if $name eq '';
@ -771,53 +771,6 @@ sub _check_zip {
return 1; return 1;
} }
sub _get_part_details {
my ($pms, $part) = @_;
#https://en.wikipedia.org/wiki/MIME#Content-Disposition
#https://github.com/mikel/mail/pull/464
my $ctt = $part->get_header('content-type');
return undef unless defined $ctt; ## no critic (ProhibitExplicitReturnUndef)
my $cte = lc($part->get_header('content-transfer-encoding') || '');
return undef unless ($cte =~ /^(?:base64|quoted\-printable)$/); ## no critic (ProhibitExplicitReturnUndef)
$ctt = _decode_part_header($part, $ctt || '');
my $name = '';
my $cttname = '';
my $ctdname = '';
if ($ctt =~ m/name\s*=\s*["']?([^"';]*)/is) {
$cttname = $1;
$cttname =~ s/\s+$//;
}
my $ctd = $part->get_header('content-disposition');
$ctd = _decode_part_header($part, $ctd || '');
if ($ctd =~ m/filename\s*=\s*["']?([^"';]*)/is) {
$ctdname = $1;
$ctdname =~ s/\s+$//;
}
if (lc $ctdname eq lc $cttname) {
$name = $ctdname;
} elsif ($ctdname eq '') {
$name = $cttname;
} elsif ($cttname eq '') {
$name = $ctdname;
} else {
if ($pms->{conf}->{olemacro_prefer_contentdisposition}) {
$name = $ctdname;
} else {
$name = $cttname;
}
}
return $ctt, $ctd, $cte, $name;
}
sub _open_zip_handle { sub _open_zip_handle {
my ($data) = @_; my ($data) = @_;
@ -1093,35 +1046,6 @@ sub _zip_error_handler {
1; 1;
} }
sub _decode_part_header {
my($part, $header_field_body) = @_;
return '' unless defined $header_field_body && $header_field_body ne '';
# deal with folding and cream the newlines and such
$header_field_body =~ s/\n[ \t]+/\n /g;
$header_field_body =~ s/\015?\012//gs;
local($1,$2,$3);
# Multiple encoded sections must ignore the interim whitespace.
# To avoid possible FPs with (\s+(?==\?))?, look for the whole RE
# separated by whitespace.
1 while $header_field_body =~
s{ ( = \? [A-Za-z0-9_-]+ \? [bqBQ] \? [^?]* \? = ) \s+
( = \? [A-Za-z0-9_-]+ \? [bqBQ] \? [^?]* \? = ) }
{$1$2}xsg;
# transcode properly encoded RFC 2047 substrings into UTF-8 octets,
# leave everything else unchanged as it is supposed to be UTF-8 (RFC 6532)
# or plain US-ASCII
$header_field_body =~
s{ (?: = \? ([A-Za-z0-9_-]+) \? ([bqBQ]) \? ([^?]*) \? = ) }
{ $part->__decode_header($1, uc($2), $3) }xsge;
return $header_field_body;
}
# Version features # Version features
sub has_olemacro_redirect_uri { 1 } sub has_olemacro_redirect_uri { 1 }
sub has_olemacro_mhtml_uri { 1 } sub has_olemacro_mhtml_uri { 1 }

View File

@ -328,9 +328,16 @@ sub _get_pdf_details {
} }
# XXX some pdf have uris but are stored inside binary data # XXX some pdf have uris but are stored inside binary data
if (keys %uris < 20 && $line =~ /(?:\/S\s{0,2}\/URI\s{0,2}|^\s*)\/URI\s{0,2}( \( .*? (?<!\\) \) | < [^>]* > )/x) { if (keys %uris < 20 && $line =~ /(?:\/S\s{0,2}\/URI\s{0,2}|^\s*)\/URI\s{0,2}( \( .*? (?<!\\) \) | < [^>]* > )|\((https?:\/\/.{8,256})\)>>/x) {
my $location = _parse_string($1); my $location;
if (defined $1 and (index($1, '.') > 0)) {
$location = _parse_string($1);
}
if (not defined($location) or index($location, '.') <= 0) {
$location = _parse_string($2);
}
next unless index($location, '.') > 0; # ignore some binary mess next unless index($location, '.') > 0; # ignore some binary mess
next if $location =~ /\0/; # ignore urls with NUL characters
if (!exists $uris{$location}) { if (!exists $uris{$location}) {
$uris{$location} = 1; $uris{$location} = 1;
dbg("pdfinfo: found URI: $location"); dbg("pdfinfo: found URI: $location");

View File

@ -31,7 +31,6 @@ Mail::SpamAssassin::Plugin::Phishing - check uris against phishing feed
ifplugin Mail::SpamAssassin::Plugin::Phishing ifplugin Mail::SpamAssassin::Plugin::Phishing
phishing_openphish_feed /etc/mail/spamassassin/openphish-feed.txt phishing_openphish_feed /etc/mail/spamassassin/openphish-feed.txt
phishing_phishtank_feed /etc/mail/spamassassin/phishtank-feed.csv phishing_phishtank_feed /etc/mail/spamassassin/phishtank-feed.csv
phishing_phishstats_feed /etc/mail/spamassassin/phishstats-feed.csv
body URI_PHISHING eval:check_phishing() body URI_PHISHING eval:check_phishing()
describe URI_PHISHING Url match phishing in feed describe URI_PHISHING Url match phishing in feed
endif endif
@ -48,9 +47,6 @@ The PhishTank free feed is updated every 1 hours and can be downloaded from
http://data.phishtank.com/data/online-valid.csv. http://data.phishtank.com/data/online-valid.csv.
To avoid download limits a registration is required. To avoid download limits a registration is required.
The PhishStats feed is updated every 90 minutes and can be downloaded from
https://phishstats.info/phish_score.csv.
=cut =cut
package Mail::SpamAssassin::Plugin::Phishing; package Mail::SpamAssassin::Plugin::Phishing;
@ -143,40 +139,6 @@ skipped.
=back =back
=cut
push(@cmds, {
setting => 'phishing_phishstats_feed',
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
}
);
=over 4
=item phishing_phishstats_feed
Absolute path of the downloaded PhishStats datafeed.
=back
=cut
push(@cmds, {
setting => 'phishing_phishstats_minscore',
is_admin => 1,
default => 6,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
}
);
=over 4
=item phishing_phishstats_minscore ( 0 - 10 ) (default: 6)
Minimum score to take into consideration for phishing uris downloaded
from PhishStats datafeed.
=back
=cut =cut
$conf->{parser}->register_commands(\@cmds); $conf->{parser}->register_commands(\@cmds);
} }
@ -189,7 +151,7 @@ sub finish_parsing_end {
sub _read_configfile { sub _read_configfile {
my ($self) = @_; my ($self) = @_;
my $conf = $self->{main}->{registryboundaries}->{conf}; my $conf = $self->{main}->{registryboundaries}->{conf};
my (@phtank_ln, @phstats_ln); my @phtank_ln;
my $stripped_cluri; my $stripped_cluri;
local *F; local *F;
@ -244,39 +206,6 @@ sub _read_configfile {
close(F) or die "error closing config file: $!"; close(F) or die "error closing config file: $!";
} }
if ( defined($conf->{phishing_phishstats_feed}) && (-f $conf->{phishing_phishstats_feed} ) ) {
open(F, '<', $conf->{phishing_phishstats_feed});
for ($!=0; <F>; $!=0) {
#skip first line
next if ( $. eq 1);
chomp;
#lines that start with pound are comments
next if(/^\s*\#/);
# CSV: Date,Score,URL,IP
@phstats_ln = split(/,/, $_);
$phstats_ln[1] =~ s/\"//g;
$phstats_ln[2] =~ s/\"//g;
if ( $conf->{phishing_phishstats_minscore} >= $phstats_ln[1] ) {
next;
}
$stripped_cluri = $phstats_ln[2];
if ( $conf->{phishing_uri_noparam} eq 1 ) {
$stripped_cluri =~ s/\?.*//;
}
my $phishdomain = $self->{main}->{registryboundaries}->uri_to_domain($phstats_ln[2]);
if ( defined $phishdomain ) {
push @{$self->{PHISHING}->{$stripped_cluri}->{phishdomain}}, $phishdomain;
push @{$self->{PHISHING}->{$stripped_cluri}->{phishinfo}->{$phishdomain}}, "PhishStats";
}
}
defined $_ || $!==0 or
$!==EBADF ? dbg("PHISHING: error reading config file: $!")
: die "error reading config file: $!";
close(F) or die "error closing config file: $!";
}
} }
sub check_phishing { sub check_phishing {

View File

@ -79,7 +79,7 @@ sub set_config {
my ($self, $conf) = @_; my ($self, $conf) = @_;
my @cmds; my @cmds;
=head1 USER OPTIONS =head1 USER SETTINGS
=over 4 =over 4
@ -91,26 +91,10 @@ Whether to use Pyzor, if it is available.
push (@cmds, { push (@cmds, {
setting => 'use_pyzor', setting => 'use_pyzor',
is_admin => 1,
default => 1, default => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL
}); });
=item pyzor_fork (0|1) (default: 1)
Instead of running Pyzor synchronously, fork separate process for it and
read the results in later (similar to async DNS lookups). Increases
throughput. Considered experimental on Windows, where default is 0.
=cut
push(@cmds, {
setting => 'pyzor_fork',
is_admin => 1,
default => am_running_on_windows()?0:1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
});
=item pyzor_count_min NUMBER (default: 5) =item pyzor_count_min NUMBER (default: 5)
This option sets how often a message's body checksum must have been This option sets how often a message's body checksum must have been
@ -124,7 +108,6 @@ set this to a relatively low value, e.g. C<5>.
push (@cmds, { push (@cmds, {
setting => 'pyzor_count_min', setting => 'pyzor_count_min',
is_admin => 1,
default => 5, default => 5,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
}); });
@ -132,7 +115,6 @@ set this to a relatively low value, e.g. C<5>.
# Deprecated setting, the name makes no sense! # Deprecated setting, the name makes no sense!
push (@cmds, { push (@cmds, {
setting => 'pyzor_max', setting => 'pyzor_max',
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
code => sub { code => sub {
my ($self, $key, $value, $line) = @_; my ($self, $key, $value, $line) = @_;
@ -157,7 +139,6 @@ result. Final decision is made by pyzor_welcomelist_factor.
push (@cmds, { push (@cmds, {
setting => 'pyzor_welcomelist_min', setting => 'pyzor_welcomelist_min',
aliases => ['pyzor_whitelist_min'], # removed in 4.1 aliases => ['pyzor_whitelist_min'], # removed in 4.1
is_admin => 1,
default => 10, default => 10,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
}); });
@ -166,7 +147,7 @@ result. Final decision is made by pyzor_welcomelist_factor.
Previously pyzor_whitelist_factor which will work interchangeably until 4.1. Previously pyzor_whitelist_factor which will work interchangeably until 4.1.
Ignore Pyzor result if REPORTCOUNT x NUMBER >= pyzor_welcomelist_min. Ignore Pyzor result if REPORTCOUNT x NUMBER E<gt>= pyzor_welcomelist_min.
For default setting this means: 50 reports requires 10 welcomelistings. For default setting this means: 50 reports requires 10 welcomelistings.
=cut =cut
@ -174,17 +155,44 @@ For default setting this means: 50 reports requires 10 welcomelistings.
push (@cmds, { push (@cmds, {
setting => 'pyzor_welcomelist_factor', setting => 'pyzor_welcomelist_factor',
aliases => ['pyzor_whitelist_factor'], # removed in 4.1 aliases => ['pyzor_whitelist_factor'], # removed in 4.1
is_admin => 1,
default => 0.2, default => 0.2,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
}); });
=back =back
=head1 ADMINISTRATOR OPTIONS =head1 ADMINISTRATOR SETTINGS
=over 4 =over 4
=item pyzor_fork (0|1) (default: 1)
Instead of running Pyzor synchronously, fork separate process for it and
read the results in later (similar to async DNS lookups). Increases
throughput. Considered experimental on Windows, where default is 0.
=cut
push(@cmds, {
setting => 'pyzor_fork',
is_admin => 1,
default => am_running_on_windows()?0:1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
});
=item pyzor_perl (0|1) (default: 0)
Instead of running Pyzor client, use a pure Perl client.
=cut
push(@cmds, {
setting => 'pyzor_perl',
is_admin => 1,
default => 0,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
});
=item pyzor_timeout n (default: 5) =item pyzor_timeout n (default: 5)
How many seconds you wait for Pyzor to complete, before scanning continues How many seconds you wait for Pyzor to complete, before scanning continues
@ -232,11 +240,11 @@ characters in the range [0-9A-Za-z =,._/-] are allowed for security reasons.
code => sub { code => sub {
my ($self, $key, $value, $line) = @_; my ($self, $key, $value, $line) = @_;
if ($value !~ m{^([0-9A-Za-z =,._/-]+)$}) { if ($value !~ m{^([0-9A-Za-z =,._/-]+)$}) {
return $Mail::SpamAssassin::Conf::INVALID_VALUE; return $Mail::SpamAssassin::Conf::INVALID_VALUE;
} }
$self->{pyzor_options} = $1; $self->{pyzor_options} = $1;
} }
}); });
=item pyzor_path STRING =item pyzor_path STRING
@ -255,38 +263,89 @@ you should use this, as the current PATH will have been cleared.
code => sub { code => sub {
my ($self, $key, $value, $line) = @_; my ($self, $key, $value, $line) = @_;
if (!defined $value || !length $value) { if (!defined $value || !length $value) {
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
} }
$value = untaint_file_path($value); $value = untaint_file_path($value);
if (!-x $value) { if (!-x $value) {
info("config: pyzor_path \"$value\" isn't an executable"); info("config: pyzor_path \"$value\" isn't an executable");
return $Mail::SpamAssassin::Conf::INVALID_VALUE; return $Mail::SpamAssassin::Conf::INVALID_VALUE;
} }
$self->{pyzor_path} = $value; $self->{pyzor_path} = $value;
} }
}); });
=item pyzor_server_file FILE
Pyzor servers configuration file path, used by Pyzor Perl implementation.
By default Pyzor will connect to public.pyzor.org on port 24441.
=cut
push (@cmds, {
setting => 'pyzor_server_file',
is_admin => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
code => sub {
my ($self, $key, $value, $line) = @_;
if (! -f $value) {
return $Mail::SpamAssassin::Conf::INVALID_VALUE;
} else {
my $i = 0;
open(PSF, '<', untaint_file_path($value));
while(<PSF>) {
chomp;
next if (/^#/);
my ($host, $port) = split(/:/, $_);
$self->{pyzor_host}[$i] = $host;
$self->{pyzor_port}[$i] = $port;
dbg("Setting Pyzor host to $host and port to $port");
$i++;
}
close(PSF);
}
}
});
$conf->{parser}->register_commands(\@cmds); $conf->{parser}->register_commands(\@cmds);
} }
sub is_pyzor_available { sub is_pyzor_available {
my ($self) = @_; my ($self) = @_;
my $pyzor = $self->{main}->{conf}->{pyzor_path} || if($self->{main}->{conf}->{pyzor_perl} and $self->{main}->{conf}->{pyzor_fork}) {
Mail::SpamAssassin::Util::find_executable_in_env_path('pyzor'); info("pyzor: pyzor_perl and pyzor_fork cannot be enabled at the same time, Pyzor support will be disabled");
return 0;
}
local $@;
if($self->{main}->{conf}->{pyzor_perl}) {
eval {
require Mail::SpamAssassin::Pyzor::Digest;
require Mail::SpamAssassin::Pyzor::Client;
};
if($@) {
$self->{pyzor_available} = 0;
dbg("pyzor: pyzor Perl module is not available");
return 0;
} else {
$self->{pyzor_available} = 1;
dbg("pyzor: pyzor Perl module is available");
return 1;
}
} else {
my $pyzor = $self->{main}->{conf}->{pyzor_path} ||
Mail::SpamAssassin::Util::find_executable_in_env_path('pyzor');
unless ($pyzor && -x $pyzor) {
dbg("pyzor: no pyzor executable found");
$self->{pyzor_available} = 0;
return 0;
}
unless ($pyzor && -x $pyzor) { # remember any found pyzor
dbg("pyzor: no pyzor executable found"); $self->{main}->{conf}->{pyzor_path} = $pyzor;
$self->{pyzor_available} = 0; dbg("pyzor: pyzor is available: $pyzor");
return 0; return 1;
} }
# remember any found pyzor
$self->{main}->{conf}->{pyzor_path} = $pyzor;
dbg("pyzor: pyzor is available: $pyzor");
return 1;
} }
sub finish_parsing_start { sub finish_parsing_start {
@ -319,17 +378,23 @@ sub check_pyzor {
$pms->{tag_data}->{PYZOR} = ''; $pms->{tag_data}->{PYZOR} = '';
# create fulltext tmpfile now (before possible forking) # create fulltext tmpfile now (before possible forking)
$pms->{pyzor_tmpfile} = $pms->create_fulltext_tmpfile(); if(!$self->{main}->{conf}->{pyzor_perl}) {
$pms->{pyzor_tmpfile} = $pms->create_fulltext_tmpfile();
}
## non-forking method ## non-forking method
if ((!$self->{main}->{conf}->{pyzor_fork}) and (!$self->{main}->{conf}->{pyzor_perl})) {
if (!$self->{main}->{conf}->{pyzor_fork}) {
my @results = $self->pyzor_lookup($pms); my @results = $self->pyzor_lookup($pms);
return $self->_check_result($pms, \@results); return $self->_check_result($pms, \@results);
} }
## forking method ## Pure Perl implementation
if($self->{main}->{conf}->{pyzor_perl}) {
my @results = $self->pyzor_lookup($pms, $full);
return $self->_check_result($pms, \@results);
}
## forking method
$pms->{pyzor_rulename} = $pms->get_current_eval_rule_name(); $pms->{pyzor_rulename} = $pms->get_current_eval_rule_name();
# create socketpair for communication # create socketpair for communication
@ -389,76 +454,121 @@ sub check_pyzor {
} }
sub pyzor_lookup { sub pyzor_lookup {
my ($self, $pms) = @_; my ( $self, $pms, $fulltext ) = @_;
my $conf = $self->{main}->{conf}; my $conf = $self->{main}->{conf};
my $timeout = $conf->{pyzor_timeout}; my $timeout = $conf->{pyzor_timeout};
my $server_port;
my @pyzor_hosts;
my @pyzor_ports;
if(defined $conf->{pyzor_host}) {
@pyzor_hosts = @{$conf->{pyzor_host}};
}
if(defined $conf->{pyzor_port}) {
@pyzor_ports = @{$conf->{pyzor_port}};
}
$pyzor_hosts[0] //= 'public.pyzor.org';
$pyzor_ports[0] //= 24441;
# note: not really tainted, this came from system configuration file if($self->{main}->{conf}->{pyzor_perl}) {
my $path = untaint_file_path($conf->{pyzor_path}); my @resp;
my $opts = untaint_var($conf->{pyzor_options}) || ''; my $cnt = 0;
my $resp;
my $ref;
$pms->enter_helper_run_mode(); my $digest = Mail::SpamAssassin::Pyzor::Digest::get( $pms, $fulltext );
my $pid; foreach my $server_host ( @pyzor_hosts ) {
my @resp; $server_port = $pyzor_ports[$cnt];
my $timer = Mail::SpamAssassin::Timeout->new( $cnt++;
{ secs => $timeout, deadline => $pms->{master_deadline} }); my $client = Mail::SpamAssassin::Pyzor::Client->new( 'timeout' => $timeout,
my $err = $timer->run_and_catch(sub { 'server_host' => $server_host,
local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; 'server_port' => $server_port );
local $@;
$ref = eval { $client->check($digest); };
# $client reply must be an hash
return 0 if (not (ref $ref eq ref {}));
if ($@) {
my $err = $@;
dbg("pyzor: opening pipe: ". $err = eval { $err->get_message() } || $err;
join(' ', $path, $opts, "check", "<".$pms->{pyzor_tmpfile}));
$pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*PYZOR, warn("pyzor: check failed: $err\n");
$pms->{pyzor_tmpfile}, 1, $path, split(' ', $opts), "check"); return 0;
$pid or die "$!\n";
# read+split avoids a Perl I/O bug (Bug 5985)
my($inbuf, $nread);
my $resp = '';
while ($nread = read(PYZOR, $inbuf, 8192)) { $resp .= $inbuf }
defined $nread or die "error reading from pipe: $!";
@resp = split(/^/m, $resp, -1);
my $errno = 0;
close PYZOR or $errno = $!;
if (proc_status_ok($?, $errno)) {
dbg("pyzor: [%s] finished successfully", $pid);
} elsif (proc_status_ok($?, $errno, 0, 1)) { # sometimes it exits with 1
dbg("pyzor: [%s] finished: %s", $pid, exit_status_str($?, $errno));
} else {
info("pyzor: [%s] error: %s", $pid, exit_status_str($?, $errno));
}
});
if (defined(fileno(*PYZOR))) { # still open
if ($pid) {
if (kill('TERM', $pid)) {
dbg("pyzor: killed stale helper [$pid]");
} else {
dbg("pyzor: killing helper application [$pid] failed: $!");
} }
# mimic pyzor client reply
# public.pyzor.org:24441\t(200, 'OK')\t1\t0
$resp .= "$server_host:$server_port\t($ref->{'Code'}, '" . $ref->{'Diag'} . "')\t$ref->{'Count'}\t$ref->{'WL-Count'}\n";
undef $ref;
} }
my $errno = 0; @resp = split(/^/m, $resp, -1);
close PYZOR or $errno = $!; return @resp;
proc_status_ok($?, $errno) } else {
or info("pyzor: [%s] error: %s", $pid, exit_status_str($?, $errno)); # note: not really tainted, this came from system configuration file
my $path = untaint_file_path($conf->{pyzor_path});
my $opts = untaint_var($conf->{pyzor_options}) || '';
$pms->enter_helper_run_mode();
my $pid;
my @resp;
my $timer = Mail::SpamAssassin::Timeout->new(
{ secs => $timeout, deadline => $pms->{master_deadline} });
my $err = $timer->run_and_catch(sub {
local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" };
dbg("pyzor: opening pipe: ".
join(' ', $path, $opts, "check", "<".$pms->{pyzor_tmpfile}));
$pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*PYZOR,
$pms->{pyzor_tmpfile}, 1, $path, split(' ', $opts), "check");
$pid or die "$!\n";
# read+split avoids a Perl I/O bug (Bug 5985)
my($inbuf, $nread);
my $resp = '';
while ($nread = read(PYZOR, $inbuf, 8192)) { $resp .= $inbuf }
defined $nread or die "error reading from pipe: $!";
@resp = split(/^/m, $resp, -1);
my $errno = 0;
close PYZOR or $errno = $!;
if (proc_status_ok($?, $errno)) {
dbg("pyzor: [%s] finished successfully", $pid);
} elsif (proc_status_ok($?, $errno, 0, 1)) { # sometimes it exits with 1
dbg("pyzor: [%s] finished: %s", $pid, exit_status_str($?, $errno));
} else {
info("pyzor: [%s] error: %s", $pid, exit_status_str($?, $errno));
}
});
if (defined(fileno(*PYZOR))) { # still open
if ($pid) {
if (kill('TERM', $pid)) {
dbg("pyzor: killed stale helper [$pid]");
} else {
dbg("pyzor: killing helper application [$pid] failed: $!");
}
}
my $errno = 0;
close PYZOR or $errno = $!;
proc_status_ok($?, $errno)
or info("pyzor: [%s] error: %s", $pid, exit_status_str($?, $errno));
}
$pms->leave_helper_run_mode();
if ($timer->timed_out()) {
dbg("pyzor: check timed out after $timeout seconds");
return ();
} elsif ($err) {
chomp $err;
info("pyzor: check failed: $err");
return ();
}
return @resp;
} }
$pms->leave_helper_run_mode();
if ($timer->timed_out()) {
dbg("pyzor: check timed out after $timeout seconds");
return ();
} elsif ($err) {
chomp $err;
info("pyzor: check failed: $err");
return ();
}
return @resp;
} }
sub check_tick { sub check_tick {
@ -537,7 +647,7 @@ sub _check_forked_result {
sub _check_result { sub _check_result {
my ($self, $pms, $results) = @_; my ($self, $pms, $results) = @_;
if (!@$results) { unless ((defined $results) && @$results) {
dbg("pyzor: no response from server"); dbg("pyzor: no response from server");
return 0; return 0;
} }
@ -569,7 +679,7 @@ sub _check_result {
my $wl_min = $conf->{pyzor_welcomelist_min}; my $wl_min = $conf->{pyzor_welcomelist_min};
my $wl_limit = $count_wl >= $wl_min ? my $wl_limit = $count_wl >= $wl_min ?
$count * $conf->{pyzor_welcomelist_factor} : 0; $count * $conf->{pyzor_welcomelist_factor} : 0;
dbg("pyzor: result: COUNT=$count/$count_min WELCOMELIST=$count_wl/$wl_min/%.1f", dbg("pyzor: result: COUNT=$count/$count_min WELCOMELIST=$count_wl/$wl_min/%.1f",
$wl_limit); $wl_limit);
@ -606,84 +716,138 @@ sub plugin_report {
return if $options->{report}->{options}->{dont_report_to_pyzor}; return if $options->{report}->{options}->{dont_report_to_pyzor};
return if !$self->is_pyzor_available(); return if !$self->is_pyzor_available();
# use temporary file: open2() is unreliable due to buffering under spamd if($self->{main}->{conf}->{pyzor_perl}) {
my $tmpf = $options->{report}->create_fulltext_tmpfile($options->{text}); if (!$options->{report}->{options}->{dont_report_to_pyzor} && $self->is_pyzor_available()) {
if ($self->pyzor_report($options, $tmpf)) { if ($self->pyzor_report($options)) {
$options->{report}->{report_available} = 1; $options->{report}->{report_available} = 1;
info("reporter: spam reported to Pyzor"); info("reporter: spam reported to Pyzor");
$options->{report}->{report_return} = 1; $options->{report}->{report_return} = 1;
} else {
info("reporter: could not report spam to Pyzor");
}
}
} else {
# use temporary file: open2() is unreliable due to buffering under spamd
my $tmpf = $options->{report}->create_fulltext_tmpfile($options->{text});
if ($self->pyzor_report($options, $tmpf)) {
$options->{report}->{report_available} = 1;
info("reporter: spam reported to Pyzor");
$options->{report}->{report_return} = 1;
}
else {
info("reporter: could not report spam to Pyzor");
}
$options->{report}->delete_fulltext_tmpfile($tmpf);
} }
else {
info("reporter: could not report spam to Pyzor");
}
$options->{report}->delete_fulltext_tmpfile($tmpf);
return 1; return 1;
} }
sub pyzor_report { sub pyzor_report {
my ($self, $options, $tmpf) = @_; my ( $self, $options, $tmpf ) = @_;
# note: not really tainted, this came from system configuration file my $conf = $self->{main}->{conf};
my $path = untaint_file_path($options->{report}->{conf}->{pyzor_path});
my $opts = untaint_var($options->{report}->{conf}->{pyzor_options}) || ''; my $server_port;
my @pyzor_hosts;
my @pyzor_ports;
if(defined $conf->{pyzor_host}) {
@pyzor_hosts = @{$conf->{pyzor_host}};
}
if(defined $conf->{pyzor_port}) {
@pyzor_ports = @{$conf->{pyzor_port}};
}
$pyzor_hosts[0] //= 'public.pyzor.org';
$pyzor_ports[0] //= 24441;
my $timeout = $self->{main}->{conf}->{pyzor_timeout}; my $timeout = $self->{main}->{conf}->{pyzor_timeout};
$options->{report}->enter_helper_run_mode(); if($self->{main}->{conf}->{pyzor_perl}) {
my $pms =
Mail::SpamAssassin::PerMsgStatus->new($self->{main}, $options->{msg});
my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout }); my $cnt = 0;
my $err = $timer->run_and_catch(sub { foreach my $server_host ( @pyzor_hosts ) {
$server_port = $pyzor_ports[$cnt];
$cnt++;
my $client = Mail::SpamAssassin::Pyzor::Client->new( 'timeout' => $timeout,
'server_host' => $server_host,
'server_port' => $server_port );
local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; my $digest = Mail::SpamAssassin::Pyzor::Digest::get( $pms );
dbg("pyzor: opening pipe: " . join(' ', $path, $opts, "report", "< $tmpf")); local $@;
my $ref = eval { $client->report($digest); };
if ($@) {
warn("pyzor: report failed: $@");
return 0;
}
elsif ( $ref->{'Code'} ne 200 ) {
dbg("pyzor: report failed with invalid code: $ref->{'Code'}: $ref->{'Diag'}");
return 0;
}
}
} else {
# note: not really tainted, this came from system configuration file
my $path = untaint_file_path($options->{report}->{conf}->{pyzor_path});
my $opts = untaint_var($options->{report}->{conf}->{pyzor_options}) || '';
my $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*PYZOR, my $timeout = $self->{main}->{conf}->{pyzor_timeout};
$tmpf, 1, $path, split(' ', $opts), "report");
$pid or die "$!\n";
my($inbuf,$nread,$nread_all); $nread_all = 0; $options->{report}->enter_helper_run_mode();
# response is ignored, just check its existence
while ( $nread=read(PYZOR,$inbuf,8192) ) { $nread_all += $nread }
defined $nread or die "error reading from pipe: $!";
dbg("pyzor: empty response") if $nread_all < 1; my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
my $err = $timer->run_and_catch(sub {
my $errno = 0; close PYZOR or $errno = $!; local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" };
# closing a pipe also waits for the process executing on the pipe to
# complete, no need to explicitly call waitpid dbg("pyzor: opening pipe: " . join(' ', $path, $opts, "report", "< $tmpf"));
# my $child_stat = waitpid($pid,0) > 0 ? $? : undef;
if (proc_status_ok($?,$errno, 0)) { my $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*PYZOR,
dbg("pyzor: [%s] reporter finished successfully", $pid); $tmpf, 1, $path, split(' ', $opts), "report");
} else { $pid or die "$!\n";
info("pyzor: [%s] reporter error: %s", $pid, exit_status_str($?,$errno));
my($inbuf,$nread,$nread_all); $nread_all = 0;
# response is ignored, just check its existence
while ( $nread=read(PYZOR,$inbuf,8192) ) { $nread_all += $nread }
defined $nread or die "error reading from pipe: $!";
dbg("pyzor: empty response") if $nread_all < 1;
my $errno = 0; close PYZOR or $errno = $!;
# closing a pipe also waits for the process executing on the pipe to
# complete, no need to explicitly call waitpid
# my $child_stat = waitpid($pid,0) > 0 ? $? : undef;
if (proc_status_ok($?,$errno, 0)) {
dbg("pyzor: [%s] reporter finished successfully", $pid);
} else {
info("pyzor: [%s] reporter error: %s", $pid, exit_status_str($?,$errno));
}
});
$options->{report}->leave_helper_run_mode();
if ($timer->timed_out()) {
dbg("reporter: pyzor report timed out after $timeout seconds");
return 0;
} }
}); if ($err) {
chomp $err;
$options->{report}->leave_helper_run_mode(); if ($err eq '__brokenpipe__ignore__') {
dbg("reporter: pyzor report failed: broken pipe");
if ($timer->timed_out()) { } else {
dbg("reporter: pyzor report timed out after $timeout seconds"); warn("reporter: pyzor report failed: $err\n");
return 0; }
} return 0;
if ($err) {
chomp $err;
if ($err eq '__brokenpipe__ignore__') {
dbg("reporter: pyzor report failed: broken pipe");
} else {
warn("reporter: pyzor report failed: $err\n");
} }
return 0;
} }
return 1; return 1;
} }
# Version features # Version features
sub has_fork { 1 } sub has_fork { 1 }
sub has_perl { 1 }
1; 1;

View File

@ -32,10 +32,6 @@ input is validated through reputation assignments.
See http://razor.sourceforge.net/ for more information about Razor. See http://razor.sourceforge.net/ for more information about Razor.
=head1 USER SETTINGS
=over 4
=cut =cut
package Mail::SpamAssassin::Plugin::Razor2; package Mail::SpamAssassin::Plugin::Razor2;
@ -90,6 +86,38 @@ sub set_config {
my ($self, $conf) = @_; my ($self, $conf) = @_;
my @cmds; my @cmds;
=head1 DEPENDENCIES
Razor2 requires the C<Razor2::Client::Agent> Perl module to be installed.
=head1 RULE DEFINITIONS
Razor2 calculates a signature for each part of a multipart message and then
compares those signatures to a database of known spam signatures. The server returns a confidence
value (0-100) for each part of the message. The part with the highest confidence value is used as the confidence value
for the message.
The following eval rules are provided by this plugin:
full RULENAME eval:check_razor2()
Returns true if the confidence value of the message is greater than or equal to `min_cf` as defined in
the Razor2 configuration file 'razor-agent.conf(1)'.
full RULENAME eval:check_razor2_range(<engine>,<min>,<max>)
<engine> Engine number (4, 8 or '')
<min> Minimum confidence value (0-100)
<max> Maximum confidence value (0-100)
Returns true if the spam confidence value for the message is greater than or equal to <min> and
less than or equal to <max>. If <engine> is not specified, the engine with the highest
confidence value is used.
=head1 USER SETTINGS
=over 4
=item use_razor2 (0|1) (default: 1) =item use_razor2 (0|1) (default: 1)
Whether to use Razor2, if it is available. Whether to use Razor2, if it is available.
@ -98,11 +126,16 @@ Whether to use Razor2, if it is available.
push(@cmds, { push(@cmds, {
setting => 'use_razor2', setting => 'use_razor2',
is_admin => 1,
default => 1, default => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
}); });
=back
=head1 ADMINISTRATOR SETTINGS
=over 4
=item razor_fork (0|1) (default: 1) =item razor_fork (0|1) (default: 1)
Instead of running Razor2 synchronously, fork separate process for it and Instead of running Razor2 synchronously, fork separate process for it and
@ -118,12 +151,6 @@ throughput. Considered experimental on Windows, where default is 0.
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
}); });
=back
=head1 ADMINISTRATOR SETTINGS
=over 4
=item razor_timeout n (default: 5) =item razor_timeout n (default: 5)
How many seconds you wait for Razor to complete before you go on without How many seconds you wait for Razor to complete before you go on without

View File

@ -171,7 +171,7 @@ by a local or site-specific configuration or by C<user_prefs>.
=back =back
=head1 ADMINISTRATOR OPTIONS =head1 ADMINISTRATOR SETTINGS
=over 4 =over 4

View File

@ -211,6 +211,32 @@ for that rule.
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
}); });
=item shortcircuit_min_ham_score n.nn (default: undef)
When shortcircuit_min_ham_score is set, SpamAssassin will stop processing when total score
will be lower then this value.
=cut
push (@cmds, {
setting => 'shortcircuit_min_ham_score',
default => undef,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
});
=item shortcircuit_max_spam_score n.nn (default: undef)
When shortcircuit_max_spam_score is set, SpamAssassin will stop processing when total score
will be higher then this value.
=cut
push (@cmds, {
setting => 'shortcircuit_max_spam_score',
default => undef,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
});
$conf->{parser}->register_commands(\@cmds); $conf->{parser}->register_commands(\@cmds);
} }
@ -233,18 +259,36 @@ sub hit_rule {
my $scan = $params->{permsgstatus}; my $scan = $params->{permsgstatus};
my $rule = $params->{rulename}; my $rule = $params->{rulename};
my $conf = $scan->{conf};
my $score = $params->{score};
return if $scan->{shortcircuited};
# don't s/c if we're linting # don't s/c if we're linting
return if ($self->{main}->{lint_rules}); return if ($self->{main}->{lint_rules});
# don't s/c if we're in compile_now() # don't s/c if we're in compile_now()
return if ($self->{am_compiling}); return if ($self->{am_compiling});
if((defined $conf->{shortcircuit_min_ham_score} and ($scan->{score} < $conf->{shortcircuit_min_ham_score})) or
(defined $conf->{shortcircuit_max_spam_score} and ($scan->{score} > $conf->{shortcircuit_max_spam_score}))) {
$scan->{shortcircuited} = 1;
# bug 5256: if we short-circuit, don't do auto-learning
$scan->{disable_auto_learning} = 1;
$scan->{shortcircuit_type} = ($scan->{score} < 0 ? 'ham' : 'spam');
if($scan->{shortcircuit_type} eq 'ham') {
dbg("shortcircuit: s/c due to shortcircuit_min_ham_score $conf->{shortcircuit_min_ham_score}, total score is $scan->{score}");
$scan->got_hit('SHORTCIRCUIT', '', score => -0.001);
} elsif($scan->{shortcircuit_type} eq 'spam') {
dbg("shortcircuit: s/c due to shortcircuit_max_spam_score $conf->{shortcircuit_max_spam_score}, total score is $scan->{score}");
$scan->got_hit('SHORTCIRCUIT', '', score => 0.001);
}
}
my $sctype = $scan->{conf}->{shortcircuit}->{$rule}; my $sctype = $scan->{conf}->{shortcircuit}->{$rule};
return unless $sctype; return unless $sctype;
my $conf = $scan->{conf};
my $score = $params->{score};
$scan->{shortcircuit_rule} = $rule; $scan->{shortcircuit_rule} = $rule;
my $scscore; my $scscore;
if ($sctype eq 'on') { # guess by rule score if ($sctype eq 'on') { # guess by rule score

View File

@ -44,7 +44,6 @@ package Mail::SpamAssassin::Plugin::SpamCop;
use Mail::SpamAssassin::Plugin; use Mail::SpamAssassin::Plugin;
use Mail::SpamAssassin::Logger; use Mail::SpamAssassin::Logger;
use Mail::SpamAssassin::Util qw(untaint_var); use Mail::SpamAssassin::Util qw(untaint_var);
use IO::Socket;
use strict; use strict;
use warnings; use warnings;
# use bytes; # use bytes;
@ -81,7 +80,7 @@ sub set_config {
my($self, $conf) = @_; my($self, $conf) = @_;
my @cmds; my @cmds;
=head1 USER OPTIONS =head1 USER SETTINGS
=over 4 =over 4

View File

@ -99,7 +99,7 @@ sub set_config {
my ($self, $conf) = @_; my ($self, $conf) = @_;
my @cmds; my @cmds;
=head1 USER OPTIONS =head1 USER SETTINGS
=over 4 =over 4
@ -630,6 +630,10 @@ sub check_language {
} }
} }
my $rulename = $scan->get_current_eval_rule_name();
my $matched_languages = join(' ', @matches);
$scan->test_log("Languages detected: $matched_languages", $rulename);
return 1; return 1;
} }

View File

@ -897,6 +897,48 @@ some testing it could be likely at least slightly increased.
} }
}); });
=item B<txrep_report_details>
0 | 1 | 2 (default: 0)
Add TxRep details to the rule's description in the message report or summary,
similar to how RBL rules commonly are showing listed domains.
If enabled (value 1) the identificators (From address bound to originating IP
address fraction, From address alone, domain name bound to originating IP
address fraction, originating IP address and HELO if available) used in
calculating the sender's overall reputation are listed, including the
originating IP address fraction (according to the mask settings) where
applicable.
If this option is set to 2, the listed identificators' individual mean
reputation and count are reported in addition.
Identificators and additional data will only be added to the description on a
message's initial scan. Re-processing a previously already scanned message
will not list the individual idenficators and their respective reputation
values used originally.
This option is disabled by default for now, due to potential formatting issues
caused by the number and length of additional description details.
=cut
push (@cmds, {
setting => 'txrep_report_details',
default => 0,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
code => sub {
my ($self, $key, $value, $line) = @_;
return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE
if ($value eq '');
return $Mail::SpamAssassin::Conf::INVALID_VALUE
unless ($value =~ /^[012]$/);
$self->{txrep_report_details} = $value;
}
});
=back =back
@ -1240,9 +1282,13 @@ sub check_senders_reputation {
my $autolearn = defined $self->{autolearn}; my $autolearn = defined $self->{autolearn};
$self->{last_pms} = $self->{autolearn} = undef; $self->{last_pms} = $self->{autolearn} = undef;
$self->{pms} = $pms;
# Cases where we would not be able to use TxRep # Cases where we would not be able to use TxRep
return 0 unless ($self->{conf}->{use_txrep}); if(not $self->{conf}->{use_txrep}) {
dbg("TxRep is disabled, quitting");
return 0;
}
if ($self->{conf}->{use_auto_welcomelist}) { if ($self->{conf}->{use_auto_welcomelist}) {
warn("TxRep: cannot run when Auto-Welcomelist is enabled. Please disable it!\n"); warn("TxRep: cannot run when Auto-Welcomelist is enabled. Please disable it!\n");
return 0; return 0;
@ -1294,7 +1340,7 @@ sub check_senders_reputation {
if ($self->{conf}->{txrep_track_messages}) { if ($self->{conf}->{txrep_track_messages}) {
if ($msg_id) { if ($msg_id) {
my $msg_rep = $self->check_reputations($pms, 'MSG_ID', $msg_id, undef, $date, undef); my $msg_rep = $self->check_reputations($pms, 'MSG_ID', $msg_id, undef, $date, undef);
if (defined $msg_rep && $self->count()) { if (defined $msg_rep && ($self->count() > 0)) {
if (defined $self->{learning} && !defined $self->{forgetting}) { if (defined $self->{learning} && !defined $self->{forgetting}) {
# already learned, forget only if already learned (count>1), and relearn # already learned, forget only if already learned (count>1), and relearn
# when only scanned (count=1), go ahead with normal rep scan # when only scanned (count=1), go ahead with normal rep scan
@ -1312,6 +1358,9 @@ sub check_senders_reputation {
$pms->got_hit("TXREP", "TXREP: ", ruletype => 'eval', score => sprintf("%0.3f", $delta)); $pms->got_hit("TXREP", "TXREP: ", ruletype => 'eval', score => sprintf("%0.3f", $delta));
} }
dbg("TxRep: message %s already scanned, using old data; post-TxRep score: %0.3f", $msg_id, $pms->{score} || 'undef'); dbg("TxRep: message %s already scanned, using old data; post-TxRep score: %0.3f", $msg_id, $pms->{score} || 'undef');
if (!defined $self->{txKeepStoreTied}) {
$self->finish();
}
return 0; return 0;
} }
} # no stored reputation found, go ahead with normal rep scan } # no stored reputation found, go ahead with normal rep scan
@ -1345,9 +1394,16 @@ sub check_senders_reputation {
); );
my $ip = $origip; my $ip = $origip;
my $spf_domain;
if ($signedby) { if ($signedby) {
$ip = undef; $ip = undef;
$domain = $signedby; $domain = $signedby;
} elsif ($pms->{spf_pass} && $self->{conf}->{txrep_spf} && defined $pms->{spf_sender}) {
$ip = undef;
$spf_domain = $pms->{spf_sender};
$spf_domain =~ s/^.+@//;
$signedby = 'spf-'.$spf_domain;
dbg("TxRep: email signed by spf domain $spf_domain");
} elsif ($pms->{spf_pass} && $self->{conf}->{txrep_spf}) { } elsif ($pms->{spf_pass} && $self->{conf}->{txrep_spf}) {
$ip = undef; $ip = undef;
$signedby = 'spf'; $signedby = 'spf';
@ -1463,7 +1519,7 @@ sub check_reputation {
# TEMPLATE TAGS should match [A-Z] in their name # TEMPLATE TAGS should match [A-Z] in their name
# and "_" must be avoided # and "_" must be avoided
$tag_id =~ s/_//g; $tag_id =~ s/_//g;
if (defined $found && $self->count()) { if (defined $found && ($self->count() > 0)) {
$meanrep = $self->total() / $self->count(); $meanrep = $self->total() / $self->count();
} }
if ($self->{learning} && defined $msgscore) { if ($self->{learning} && defined $msgscore) {
@ -1479,13 +1535,28 @@ sub check_reputation {
); );
} else { } else {
$self->{totalweight} += $weight; $self->{totalweight} += $weight;
if ($key eq 'MSG_ID' && $self->count() > 0) { if ($key eq 'MSG_ID' && ($self->count() > 0)) {
$delta = $self->total() / $self->count(); $delta = $self->total() / $self->count();
$pms->set_tag('TXREP'.$tag_id, sprintf("%2.1f", $delta)); $pms->set_tag('TXREP'.$tag_id, sprintf("%2.1f", $delta));
} elsif (defined $self->total()) { } elsif (defined $self->total()) {
#Bug 7164 - $msgscore undefined #Bug 7164 - $msgscore undefined
if (defined $msgscore) { # in some cases we can have negative number
$delta = ($self->total() + $msgscore) / (1 + $self->count()) - $msgscore; # even if both total and $msgscore are positive numbers
my $deltacheck;
my $skipmsgscore = 0;
if(defined $msgscore) {
$deltacheck = ($self->total() + $msgscore) / (1 + $self->count()) - $msgscore;
if(($self->total() > 0) && ($msgscore > 0) && ($deltacheck < 0)) {
$skipmsgscore = 1;
} elsif(($self->total() < 0) && ($msgscore < 0) && ($deltacheck > 0)) {
$skipmsgscore = 1;
}
}
if($skipmsgscore) {
dbg("TxRep: skipping msg score $msgscore when calculating delta");
}
if (defined $msgscore and not $skipmsgscore) {
$delta = $deltacheck;
} else { } else {
$delta = ($self->total()) / (1 + $self->count()); $delta = ($self->total()) / (1 + $self->count());
} }
@ -1506,6 +1577,26 @@ sub check_reputation {
$delta || 0, $delta || 0,
$id || 'none' $id || 'none'
); );
if ($self->{conf}->{txrep_report_details}
&& defined $id && defined $meanrep && $tag_id ne "MSGID") {
my $log = sprintf("%s: %s",
$tag_id,
(defined $ip) ? $id."|".$self->ip_to_awl_key($ip) : $id
);
if ($self->{conf}->{txrep_report_details} == 2) {
$log .= sprintf(", rep: %.2f, count: %d",
$meanrep,
$self->count() || 0
);
}
$pms->test_log($log, "TXREP");
# dbg ("TxRep: test_log: $log");
}
} }
$timer = $self->{main}->time_method('update_txrep_'.lc($key)); $timer = $self->{main}->time_method('update_txrep_'.lc($key));
if (defined $msgscore) { if (defined $msgscore) {
@ -1538,7 +1629,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}->{msgcount} : 0;}
sub total {my $self=shift; return (defined $self->{checker})? $self->{entry}->{totscore} : undef;} sub total {my $self=shift; return (defined $self->{checker})? $self->{entry}->{totscore} : undef;}
########################################################################### ###########################################################################
@ -1577,7 +1668,7 @@ sub add_score {
$self->{entry}->{msgcount} ||= 0; $self->{entry}->{msgcount} ||= 0;
# performing the dilution aging correction # performing the dilution aging correction
if (defined $self->total() && defined $self->count() && defined $self->{txrep_dilution_factor}) { if (defined $self->total() && defined $self->count() && $self->count() > 0 && defined $self->{txrep_dilution_factor}) {
my $diluted_total = my $diluted_total =
($self->count() + 1) * ($self->count() + 1) *
($self->{txrep_dilution_factor} * $self->total() + $score) / ($self->{txrep_dilution_factor} * $self->total() + $score) /
@ -1802,6 +1893,12 @@ sub pack_addr {
if ( defined $origip) {$origip = $self->ip_to_awl_key($origip);} if ( defined $origip) {$origip = $self->ip_to_awl_key($origip);}
if (!defined $origip) {$origip = 'none';} if (!defined $origip) {$origip = 'none';}
if ( $self->{conf}->{txrep_welcomelist_out} &&
defined $self->{pms}->{relays_internal} && @{$self->{pms}->{relays_internal}} &&
(!defined $self->{pms}->{relays_external} || !@{$self->{pms}->{relays_external}})
and $addr =~ /(?:[^\s\@]+)\@(?:[^\s\@]+)/) {
$origip = 'WELCOMELIST_OUT';
}
return $addr . "|ip=" . $origip; return $addr . "|ip=" . $origip;
} }

View File

@ -130,7 +130,7 @@ any of: decimal digits, 0x followed by up to 8 hexadecimal digits, or an IPv4
address in quad-dot form. The 'A' records (IPv4 dotted address) as returned address in quad-dot form. The 'A' records (IPv4 dotted address) as returned
by DNSBLs lookups are converted into a numerical form (r) and checked against by DNSBLs lookups are converted into a numerical form (r) and checked against
the specified sub-test as follows: the specified sub-test as follows:
for a range n1-n2 the following must be true: (r >= n1 && r <= n2); for a range n1-n2 the following must be true: (r E<gt>= n1 && r E<lt>= n3);
for a n/m form the following must be true: (r & m) == (n & m); for a n/m form the following must be true: (r & m) == (n & m);
for a single value in quad-dot form the following must be true: r == n; for a single value in quad-dot form the following must be true: r == n;
for a single decimal or hex form the following must be true: for a single decimal or hex form the following must be true:
@ -181,7 +181,7 @@ any of: decimal digits, 0x followed by up to 8 hexadecimal digits, or an IPv4
address in quad-dot form. The 'A' records (IPv4 dotted address) as returned address in quad-dot form. The 'A' records (IPv4 dotted address) as returned
by DNSBLs lookups are converted into a numerical form (r) and checked against by DNSBLs lookups are converted into a numerical form (r) and checked against
the specified sub-test as follows: the specified sub-test as follows:
for a range n1-n2 the following must be true: (r >= n1 && r <= n2); for a range n1-n2 the following must be true: (r E<gt>= n1 && r E<lt>= n2);
for a n/m form the following must be true: (r & m) == (n & m); for a n/m form the following must be true: (r & m) == (n & m);
for a single value in quad-dot form the following must be true: r == n; for a single value in quad-dot form the following must be true: r == n;
for a single decimal or hex form the following must be true: for a single decimal or hex form the following must be true:
@ -221,7 +221,7 @@ Specify a RHSBL-style domain-NS lookup, as above, with a sub-test.
C<NAME_OF_RULE> is the name of the rule to be used, C<rhsbl_zone> is the zone C<NAME_OF_RULE> is the name of the rule to be used, C<rhsbl_zone> is the zone
to look up domain names in, and C<lookuptype> is the type of lookup (B<TXT> or to look up domain names in, and C<lookuptype> is the type of lookup (B<TXT> or
B<A>). C<subtest> is the sub-test to run against the returned data; see B<A>). C<subtest> is the sub-test to run against the returned data; see
<urirhssub>. C<urirhssub>.
Note that, as with C<urirhsbl>, you must also define a body-eval rule calling Note that, as with C<urirhsbl>, you must also define a body-eval rule calling
C<check_uridnsbl()> to use this. C<check_uridnsbl()> to use this.
@ -246,7 +246,7 @@ Specify a RHSBL-style domain-NS lookup, as above, with a sub-test.
C<NAME_OF_RULE> is the name of the rule to be used, C<rhsbl_zone> is the zone C<NAME_OF_RULE> is the name of the rule to be used, C<rhsbl_zone> is the zone
to look up domain names in, and C<lookuptype> is the type of lookup (B<TXT> or to look up domain names in, and C<lookuptype> is the type of lookup (B<TXT> or
B<A>). C<subtest> is the sub-test to run against the returned data; see B<A>). C<subtest> is the sub-test to run against the returned data; see
<urirhssub>. C<urirhssub>.
Note that, as with C<urirhsbl>, you must also define a body-eval rule calling Note that, as with C<urirhsbl>, you must also define a body-eval rule calling
C<check_uridnsbl()> to use this. C<check_uridnsbl()> to use this.
@ -296,6 +296,10 @@ The maximum number of domains to look up.
Include DKIM uris in lookups. This option is documented in Include DKIM uris in lookups. This option is documented in
Mail::SpamAssassin::Conf. Mail::SpamAssassin::Conf.
=item uridnsbl_skip_mailto ( 0 / 1) (default: 1)
Skip mailto links on uris lookups.
=back =back
=head1 NOTES =head1 NOTES
@ -419,9 +423,13 @@ sub check_dnsbl {
# 3: !a_empty # 3: !a_empty
# 4: parsed # 4: parsed
# 5: a_empty # 5: a_empty
while (my($uri, $info) = each %{$uris}) { my %huris = %{$uris};
foreach my $uri (keys %huris) {
my $info = $huris{$uri};
# we want to skip mailto: uris # we want to skip mailto: uris
next if ($uri =~ /^mailto:/i); if ($conf->{uridnsbl_skip_mailto}) {
next if ($uri =~ /^mailto:/i);
}
# no hosts/domains were found via this uri, so skip # no hosts/domains were found via this uri, so skip
next unless ($info->{hosts}); next unless ($info->{hosts});
@ -558,6 +566,13 @@ sub set_config {
type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL, type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
}); });
push(@cmds, {
setting => 'uridnsbl_skip_mailto',
is_admin => 1,
default => 1,
type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
});
push(@cmds, { push(@cmds, {
setting => 'uridnsbl_max_domains', setting => 'uridnsbl_max_domains',
is_admin => 1, is_admin => 1,
@ -1187,5 +1202,6 @@ sub has_subtest_for_ranges { 1 }
sub has_uridnsbl_for_a { 1 } # uridnsbl rules recognize tflags 'a' and 'ns' sub has_uridnsbl_for_a { 1 } # uridnsbl rules recognize tflags 'a' and 'ns'
sub has_uridnsbl_a_ns { 1 } # has an actually working 'a' flag, unlike above :-( sub has_uridnsbl_a_ns { 1 } # has an actually working 'a' flag, unlike above :-(
sub has_tflags_notrim { 1 } # Bug 7835 sub has_tflags_notrim { 1 } # Bug 7835
sub has_uridnsbl_skip_mailto { 1 }
1; 1;

View File

@ -47,7 +47,7 @@ C<cleaned> is a list including the raw URI and various cleaned
versions of the raw URI (http://spamassassin.apache%2Eorg/, versions of the raw URI (http://spamassassin.apache%2Eorg/,
https://spamassassin.apache.org/). https://spamassassin.apache.org/).
C<text> is the anchor text(s) (text between <a> and </a>) that C<text> is the anchor text(s) (text between E<lt>aE<gt> and E<lt>/aE<gt>) that
linked to the raw URI. linked to the raw URI.
C<domain> is the domain(s) found in the cleaned URIs, as trimmed to C<domain> is the domain(s) found in the cleaned URIs, as trimmed to
@ -156,14 +156,13 @@ sub set_config {
sub check_uri_detail { sub check_uri_detail {
my ($self, $permsg) = @_; my ($self, $permsg) = @_;
my $test = $permsg->{current_rule_name};
my $rule = $permsg->{conf}->{uri_detail}->{$test};
my %uri_detail = %{ $permsg->get_uri_detail_list() }; my %uri_detail = %{ $permsg->get_uri_detail_list() };
while (my ($raw, $info) = each %uri_detail) { while (my ($raw, $info) = each %uri_detail) {
my $test = $permsg->{current_rule_name}; dbg("uri: running uri_detail $test: $raw");
dbg("uri: running $test\n");
my $rule = $permsg->{conf}->{uri_detail}->{$test};
if (exists $rule->{raw}) { if (exists $rule->{raw}) {
my($op,$patt) = @{$rule->{raw}}; my($op,$patt) = @{$rule->{raw}};
@ -235,13 +234,7 @@ sub check_uri_detail {
dbg("uri: host matched: '%s' %s /%s/", $match,$op,$patt); dbg("uri: host matched: '%s' %s /%s/", $match,$op,$patt);
} }
if (would_log('dbg', 'rules') > 1) { dbg("uri: all criteria for $test met - HIT");
dbg("uri: criteria for $test met");
}
# reset hash
keys %uri_detail;
return 1; return 1;
} }

View File

@ -23,7 +23,7 @@ URILocalBL - blocklist URIs using local information (ISP names, address lists, a
This plugin creates some new rule test types, such as "uri_block_cc", This plugin creates some new rule test types, such as "uri_block_cc",
"uri_block_cidr", and "uri_block_isp". These rules apply to the URIs "uri_block_cidr", and "uri_block_isp". These rules apply to the URIs
found in the HTML portion of a message, i.e. <a href=...> markup. found in the HTML portion of a message, i.e. E<lt>a href=...E<gt> markup.
loadplugin Mail::SpamAssassin::Plugin::URILocalBL loadplugin Mail::SpamAssassin::Plugin::URILocalBL

View File

@ -0,0 +1,56 @@
package Mail::SpamAssassin::Pyzor;
# Copyright 2018 cPanel, LLC.
# All rights reserved.
# http://cpanel.net
#
# <@LICENSE>
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# </@LICENSE>
#
use strict;
use warnings;
our $VERSION = '0.06_01';
=encoding utf-8
=head1 NAME
Mail::SpamAssassin::Pyzor - Pyzor spam filtering in Perl
=head1 DESCRIPTION
This distribution contains Perl implementations of parts of
L<Pyzor|http://pyzor.org>, a tool for use in spam email filtering.
It is intended for use with L<Mail::SpamAssassin> but may be useful
in other contexts.
See the following modules for information on specific tools that
the distribution includes:
=over
=item * L<Mail::SpamAssassin::Pyzor::Client>
=item * L<Mail::SpamAssassin::Pyzor::Digest>
=back
=cut
1;

View File

@ -0,0 +1,419 @@
package Mail::SpamAssassin::Pyzor::Client;
# Copyright 2018 cPanel, LLC.
# All rights reserved.
# http://cpanel.net
#
# <@LICENSE>
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# </@LICENSE>
#
use strict;
use warnings;
=encoding utf-8
=head1 NAME
Mail::SpamAssassin::Pyzor::Client - Pyzor client logic
=head1 SYNOPSIS
use Mail::SpamAssassin::Pyzor::Client ();
use Mail::SpamAssassin::Pyzor::Digest ();
my $client = Mail::SpamAssassin::Pyzor::Client->new();
my $digest = Mail::SpamAssassin::Pyzor::Digest::get( $msg );
my $check_ref = $client->check($digest);
die $check_ref->{'Diag'} if $check_ref->{'Code'} ne '200';
my $report_ref = $client->report($digest);
die $report_ref->{'Diag'} if $report_ref->{'Code'} ne '200';
=head1 DESCRIPTION
A bare-bones L<Pyzor|http://pyzor.org> client that currently only
implements the functionality needed for L<Mail::SpamAssassin>.
=head1 PROTOCOL DETAILS
The Pyzor protocol is not a published standard, and there appears to be
no meaningful public documentation. What follows is enough information,
largely gleaned through forum posts and reverse engineering, to facilitate
effective use of this module:
Pyzor is an RPC-oriented, message-based protocol. Each message
is a simple dictionary of 7-bit ASCII keys and values. Server responses
always include at least the following:
=over
=item * C<Code> - Similar to HTTP status codes; anything besides C<200>
is an error.
=item * C<Diag> - Similar to HTTP status reasons: a text description
of the status.
=back
(NB: There are additional standard response headers that are useful only for
the protocol itself and thus are not part of this module's returns.)
=head2 Reliability
Pyzor uses UDP rather than TCP, so no message is guaranteed to reach its
destination. A transmission failure can happen in either the request or
the response; in either case, a timeout error will result. Such errors
are represented as thrown instances of L<Mail::Pyzor::X::Timeout>.
=cut
#----------------------------------------------------------------------
our $VERSION = '0.04';
our $DEFAULT_SERVER_HOST = 'public.pyzor.org';
our $DEFAULT_SERVER_PORT = 24441;
our $DEFAULT_USERNAME = 'anonymous';
our $DEFAULT_PASSWORD = '';
our $DEFAULT_OP_SPEC = '20,3,60,3';
our $PYZOR_PROTOCOL_VERSION = 2.1;
our $DEFAULT_TIMEOUT = 3.5;
our $READ_SIZE = 8192;
use IO::Socket::INET ();
use Digest::SHA qw(sha1 sha1_hex);
use Mail::SpamAssassin::Util qw(untaint_var);
my @hash_order = ( 'Op', 'Op-Digest', 'Op-Spec', 'Thread', 'PV', 'User', 'Time', 'Sig' );
#----------------------------------------------------------------------
=head1 CONSTRUCTOR
=head2 new(%OPTS)
Create a new pyzor client.
=over 2
=item Input
%OPTS are (all optional):
=over 3
=item * C<server_host> - The pyzor server host to connect to (default is
C<public.pyzor.org>)
=item * C<server_port> - The pyzor server port to connect to (default is
24441)
=item * C<username> - The username to present to the pyzor server (default
is C<anonymous>)
=item * C<password> - The password to present to the pyzor server (default
is empty)
=item * C<timeout> - The maximum time, in seconds, to wait for a response
from the pyzor server (defeault is 3.5)
=back
=item Output
=over 3
Returns a L<Mail::SpamAssassin::Pyzor::Client> object.
=back
=back
=cut
sub new {
my ( $class, %OPTS ) = @_;
$OPTS{'server_host'} = untaint_var($OPTS{'server_host'});
$OPTS{'server_port'} = untaint_var($OPTS{'server_port'});
$OPTS{'username'} = untaint_var($OPTS{'username'});
$OPTS{'password'} = untaint_var($OPTS{'password'});
$OPTS{'timeout'} = untaint_var($OPTS{'timeout'});
return bless {
'server_host' => $OPTS{'server_host'} || $DEFAULT_SERVER_HOST,
'server_port' => $OPTS{'server_port'} || $DEFAULT_SERVER_PORT,
'username' => $OPTS{'username'} || $DEFAULT_USERNAME,
'password' => $OPTS{'password'} || $DEFAULT_PASSWORD,
'op_spec' => $DEFAULT_OP_SPEC,
'timeout' => $OPTS{'timeout'} || $DEFAULT_TIMEOUT,
}, $class;
}
#----------------------------------------------------------------------
=head1 REQUEST METHODS
=head2 report($digest)
Report the digest of a spam message to the pyzor server. This function
will throw if a messaging failure or timeout happens.
=over 2
=item Input
=over 3
=item $digest C<SCALAR>
The message digest to report, as given by
C<Mail::SpamAssassin::Pyzor::Digest::get()>.
=back
=item Output
=over 3
=item C<HASHREF>
Returns a hashref of the standard attributes noted above.
=back
=back
=cut
sub report {
my ( $self, $digest ) = @_;
my $msg_ref = $self->_get_base_msg( 'report', $digest );
$msg_ref->{'Op-Spec'} = $self->{'op_spec'};
return $self->_send_receive_msg($msg_ref);
}
=head2 check($digest)
Check the digest of a message to see if
the pyzor server has a report for it. This function
will throw if a messaging failure or timeout happens.
=over 2
=item Input
=over 3
=item $digest C<SCALAR>
The message digest to check, as given by
C<Mail::SpamAssassin::Pyzor::Digest::get()>.
=back
=item Output
=over 3
=item C<HASHREF>
Returns a hashref of the standard attributes noted above
as well as the following:
=over
=item * C<Count> - The number of reports the server has received
for the given digest.
=item * C<WL-Count> - The number of whitelist requests the server has received
for the given digest.
=back
=back
=back
=cut
sub check {
my ( $self, $digest ) = @_;
return $self->_send_receive_msg( $self->_get_base_msg( 'check', $digest ) );
}
# ----------------------------------------
sub _send_receive_msg {
my ( $self, $msg_ref ) = @_;
my $thread_id = $msg_ref->{'Thread'} or warn 'No thread ID?';
$self->_sign_msg($msg_ref);
return $self->_do_send_receive(
$self->_generate_packet_from_message($msg_ref) . "\n\n",
$thread_id,
);
}
sub _get_base_msg {
my ( $self, $op, $digest ) = @_;
die "Implementor error: op is required" if !$op;
die "error: digest is required" if !$digest;
return {
'User' => $self->{'_username'},
'PV' => $PYZOR_PROTOCOL_VERSION,
'Time' => time(),
'Op' => $op,
'Op-Digest' => $digest,
'Thread' => $self->_generate_thread_id()
};
}
sub _do_send_receive {
my ( $self, $packet, $thread_id ) = @_;
my $sock = $self->_get_connection_or_die();
$self->_send_packet( $sock, $packet );
my $response = $self->_receive_packet( $sock, $thread_id );
return 0 if not defined $response;
my $resp_hr = { map { ( split(m{: }) )[ 0, 1 ] } split( m{\n}, $response ) };
delete $resp_hr->{'Thread'};
my $response_pv = delete $resp_hr->{'PV'};
if ( $PYZOR_PROTOCOL_VERSION ne $response_pv ) {
warn "Unexpected protocol version ($response_pv) in Pyzor response!";
}
return $resp_hr;
}
sub _receive_packet {
my ( $self, $sock, $thread_id ) = @_;
my $timeout = $self->{'timeout'} * 1000;
my $end_time = time + $self->{'timeout'};
$sock->blocking(0);
my $response = '';
my $rout = '';
my $rin = '';
vec( $rin, fileno($sock), 1 ) = 1;
while (1) {
my $time_left = $end_time - time;
if ( $time_left <= 0 ) {
warn("Did not receive a response from the pyzor server $self->{'server_host'}:$self->{'server_port'} for $self->{'timeout'} seconds!");
return;
}
my $bytes = sysread( $sock, $response, $READ_SIZE, length $response );
if ( !defined($bytes) && !$!{'EAGAIN'} && !$!{'EWOULDBLOCK'} ) {
warn "read from socket: $!";
}
if ( index( $response, "\n\n" ) > -1 ) {
# Reject the response unless its thread ID matches what we sent.
# This prevents confusion among concurrent Pyzor requests.
if ( index( $response, "\nThread: $thread_id\n" ) != -1 ) {
last;
}
else {
$response = '';
}
}
my $found = select( $rout = $rin, undef, undef, $time_left );
warn "select(): $!" if $found == -1;
}
return $response;
}
sub _send_packet {
my ( $self, $sock, $packet ) = @_;
$sock->blocking(1);
syswrite( $sock, $packet ) or warn "write to socket: $!";
return;
}
sub _get_connection_or_die {
my ($self) = @_;
# clear the socket
undef $self->{'_sock_pid'};
undef $self->{'_sock'};
$self->{'_sock_pid'} ||= $$;
$self->{'_sock'} ||= IO::Socket::INET->new(
'PeerHost' => $self->{'server_host'},
'PeerPort' => $self->{'server_port'},
'Proto' => 'udp'
) or die "Cannot connect to $self->{'server_host'}:$self->{'server_port'}: $@ $!";
return $self->{'_sock'};
}
sub _sign_msg {
my ( $self, $msg_ref ) = @_;
$msg_ref->{'Sig'} = lc Digest::SHA::sha1_hex(
Digest::SHA::sha1( $self->_generate_packet_from_message($msg_ref) )
);
return 1;
}
sub _generate_packet_from_message {
my ( $self, $msg_ref ) = @_;
return join( "\n", map { "$_: $msg_ref->{$_}" } grep { length $msg_ref->{$_} } @hash_order );
}
sub _generate_thread_id {
my $RAND_MAX = 2**16;
my $val = 0;
$val = int rand($RAND_MAX) while $val < 1024;
return $val;
}
sub _get_user_pass_hash_key {
my ($self) = @_;
return lc Digest::SHA::sha1_hex( $self->{'username'} . ':' . $self->{'password'} );
}
1;

View File

@ -0,0 +1,99 @@
package Mail::SpamAssassin::Pyzor::Digest;
# Copyright 2018 cPanel, LLC.
# All rights reserved.
# http://cpanel.net
#
# <@LICENSE>
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# </@LICENSE>
#
use strict;
use warnings;
=encoding utf-8
=head1 NAME
Mail::SpamAssassin::Pyzor::Digest - Pyzor Digest module
=head1 SYNOPSIS
my $digest = Mail::SpamAssassin::Pyzor::Digest::get( $mime_text );
=head1 DESCRIPTION
A reimplementation of L<https://github.com/SpamExperts/pyzor/blob/master/pyzor/digest.py>.
=cut
#----------------------------------------------------------------------
use Mail::SpamAssassin::Pyzor::Digest::Pieces ();
use Digest::SHA qw(sha1_hex);
our $VERSION = '0.03';
#----------------------------------------------------------------------
=head1 FUNCTIONS
=head2 $hex = get( $MSG )
This takes an email message in raw MIME text format (i.e., as saved in the
standard mbox format) and returns the message's Pyzor digest in lower-case
hexadecimal.
The output from this function should normally be identical to that of
the C<pyzor> script's C<digest> command. It is suitable for use in
L<Mail::SpamAssassin::Pyzor::Client>'s request methods.
=cut
sub get {
my ($pms) = @_;
return Digest::SHA::sha1_hex( ${ _get_predigest( $pms ) } );
}
# NB: This is called from the test.
sub _get_predigest { ## no critic qw(RequireArgUnpacking)
my ($pms) = @_;
my $parsed = $pms->get_message();
my @lines;
my $payloads_ar = Mail::SpamAssassin::Pyzor::Digest::Pieces::digest_payloads($parsed);
for my $payload (@$payloads_ar) {
my @p_lines = Mail::SpamAssassin::Pyzor::Digest::Pieces::splitlines($payload);
for my $line (@p_lines) {
Mail::SpamAssassin::Pyzor::Digest::Pieces::normalize($line);
next if !Mail::SpamAssassin::Pyzor::Digest::Pieces::should_handle_line($line);
# Make sure we have an octet string.
utf8::encode($line) if utf8::is_utf8($line);
push @lines, $line;
}
}
my $digest_sr = Mail::SpamAssassin::Pyzor::Digest::Pieces::assemble_lines( \@lines );
return $digest_sr;
}
1;

View File

@ -0,0 +1,311 @@
package Mail::SpamAssassin::Pyzor::Digest::Pieces;
# Copyright 2018 cPanel, LLC.
# All rights reserved.
# http://cpanel.net
#
# <@LICENSE>
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# </@LICENSE>
#
use strict;
use warnings;
=encoding utf-8
=head1 NAME
Mail::SpamAssassin::Pyzor::Digest::Pieces - Pyzor backend logic module
=head1 DESCRIPTION
This module houses backend logic for L<Mail::SpamAssassin::Pyzor::Digest>.
It reimplements logic found in pyzor's F<digest.py> module
(L<https://github.com/SpamExperts/pyzor/blob/master/pyzor/digest.py>).
=cut
#----------------------------------------------------------------------
use Encode ();
our $VERSION = '0.03';
# each tuple is [ offset, length ]
use constant _HASH_SPEC => ( [ 20, 3 ], [ 60, 3 ] );
use constant {
_MIN_LINE_LENGTH => 8,
_ATOMIC_NUM_LINES => 4,
};
#----------------------------------------------------------------------
=head1 FUNCTIONS
=head2 $strings_ar = digest_payloads( $EMAIL_MIME )
This imitates the corresponding object method in F<digest.py>.
It returns a reference to an array of strings. Each string can be either
a byte string or a character string (e.g., UTF-8 decoded).
NB: RFC 2822 stipulates that message bodies should use CRLF
line breaks, not plain LF (nor plain CR).
We will thus convert any plain CRs in a quoted-printable message
body into CRLF. Python, though, doesn't do this, so the output of
our implementation of C<digest_payloads()> diverges from that of the Python
original. It doesn't ultimately make a difference since the line-ending
whitespace gets trimmed regardless, but it's necessary to factor in when
comparing the output of our implementation with the Python output.
=cut
sub digest_payloads {
my ($parsed) = @_;
my @subparts;
foreach my $part ($parsed->find_parts(qr/./, 1)) {
push(@subparts, $part);
}
my @payloads;
foreach my $p (@subparts) {
my ( $main_type, $subtype, $encoding, $encode_check ) = parse_content_type( $p->{'type'} );
my $payload;
if ( $main_type eq 'text' ) {
if ( $subtype eq 'plain' ) {
$payload = $p->{'decoded'};
$payload =~ s/\\'/\'/gx;
} else {
$payload = $p->{'rendered'};
}
utf8::upgrade($payload) if defined $payload;
if ( $subtype eq 'html' ) {
require Mail::SpamAssassin::Pyzor::Digest::StripHtml;
$payload = Mail::SpamAssassin::Pyzor::Digest::StripHtml::strip($payload);
}
}
else {
# This does no decoding, even of, e.g., quoted-printable or base64.
$payload = $p->{'pristine_body'};
}
next if not defined $payload;
push @payloads, $payload;
}
return \@payloads;
}
#----------------------------------------------------------------------
=head2 normalize( $STRING )
This imitates the corresponding object method in F<digest.py>.
It modifies C<$STRING> in-place.
As with the original implementation, if C<$STRING> contains (decoded)
Unicode characters, those characters will be parsed accordingly. So:
$str = "123\xc2\xa0"; # [ c2 a0 ] == \u00a0, non-breaking space
normalize($str);
The above will leave C<$str> alone, but this:
utf8::decode($str);
normalize($str);
... will trim off the last two bytes from C<$str>.
=cut
sub normalize { ## no critic qw( Subroutines::RequireArgUnpacking )
# NULs are bad, mm-kay?
$_[0] =~ tr<\0><>d;
# NB: Python's \s without re.UNICODE is the same as Perl's \s
# with the /a modifier.
#
# https://docs.python.org/2/library/re.html
# https://perldoc.perl.org/perlrecharclass.html#Backslash-sequences
# Python: re.compile(r'\S{10,}')
$_[0] =~ s<\S{10,}><>ag;
# Python: re.compile(r'\S+@\S+')
$_[0] =~ s<\S+ @ \S+><>agx;
# Python: re.compile(r'[a-z]+:\S+', re.IGNORECASE)
$_[0] =~ s<[a-zA-Z]+ : \S+><>agx;
# (from digest.py ...)
# Make sure we do the whitespace last because some of the previous
# patterns rely on whitespace.
$_[0] =~ tr< \x09-\x0d><>d;
# This is fun. digest.py's normalize() does a non-UNICODE whitespace
# strip, then calls strip() on the string, which *will* strip Unicode
# whitespace from the ends.
$_[0] =~ s<\A\s+><>;
$_[0] =~ s<\s+\z><>;
return;
}
#----------------------------------------------------------------------
=head2 $yn = should_handle_line( $STRING )
This imitates the corresponding object method in F<digest.py>.
It returns a boolean.
=cut
sub should_handle_line {
return $_[0] && length( $_[0] ) >= _MIN_LINE_LENGTH();
}
#----------------------------------------------------------------------
=head2 $sr = assemble_lines( \@LINES )
This assembles a string buffer out of @LINES. The string is the buffer
of octets that will be hashed to produce the message digest.
Each member of @LINES is expected to be an B<octet string>, not a
character string.
=cut
sub assemble_lines {
my ($lines_ar) = @_;
if ( @$lines_ar <= _ATOMIC_NUM_LINES() ) {
# cf. handle_atomic() in digest.py
return \join( q<>, @$lines_ar );
}
#----------------------------------------------------------------------
# cf. handle_atomic() in digest.py
my $str = q<>;
for my $ofs_len ( _HASH_SPEC() ) {
my ( $offset, $length ) = @$ofs_len;
for my $i ( 0 .. ( $length - 1 ) ) {
my $idx = int( $offset * @$lines_ar / 100 ) + $i;
next if !defined $lines_ar->[$idx];
$str .= $lines_ar->[$idx];
}
}
return \$str;
}
#----------------------------------------------------------------------
=head2 ($main, $sub, $encoding, $checkval) = parse_content_type( $CONTENT_TYPE )
=cut
use constant _QUOTED_PRINTABLE_NAMES => (
"quopri-codec",
"quopri",
"quoted-printable",
"quotedprintable",
);
# Make Encode::decode() ignore anything that doesn't fit the
# given encoding.
use constant _encode_check_ignore => q<>;
sub parse_content_type {
my ($content_type) = @_;
# text/plain; charset=us-ascii
my $ct_parse;
if($content_type =~ /(\w+)\/(\w+); charset=(.*)/) {
$ct_parse->{type} = $1;
$ct_parse->{subtype} = $2;
$ct_parse->{'attributes'}{'charset'} = $3;
} elsif($content_type =~ /(\w+)\/(\w+)/) {
$ct_parse->{type} = $1;
$ct_parse->{subtype} = $2;
$ct_parse->{'attributes'}{'charset'} = 'us-ascii';
} else {
$ct_parse->{type} = 'text';
$ct_parse->{subtype} = 'plain';
$ct_parse->{'attributes'}{'charset'} = 'us-ascii';
}
my $main = $ct_parse->{'type'} || q<>;
my $sub = $ct_parse->{'subtype'} || q<>;
my $encoding = $ct_parse->{'attributes'}{'charset'};
my $checkval;
if ($encoding) {
# Lower-case everything, convert underscore to dash, and remove NUL.
$encoding =~ tr<A-Z_\0><a-z->d;
# Apparently pyzor accommodates messages that put the transfer
# encoding in the Content-Type.
if ( grep { $_ eq $encoding } _QUOTED_PRINTABLE_NAMES() ) {
$checkval = Encode::FB_CROAK();
}
}
else {
$encoding = 'ascii';
}
# Match Python .decode()'s 'ignore' behavior
$checkval ||= \&_encode_check_ignore;
return ( $main, $sub, $encoding, $checkval );
}
#----------------------------------------------------------------------
=head2 @lines = splitlines( $TEXT )
Imitates C<str.splitlines()>. (cf. C<pydoc str>)
Returns a plain list in list context. Returns the number of
items to be returned in scalar context.
=cut
sub splitlines {
return split m<\r\n?|\n>, $_[0] if defined $_[0];
}
1;

View File

@ -0,0 +1,177 @@
package Mail::SpamAssassin::Pyzor::Digest::StripHtml;
# Copyright 2018 cPanel, LLC.
# All rights reserved.
# http://cpanel.net
#
# <@LICENSE>
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# </@LICENSE>
#
use strict;
use warnings;
=encoding utf-8
=head1 NAME
Mail::SpamAssassin::Pyzor::Digest::StripHtml - Pyzor HTML-stripping module
=head1 SYNOPSIS
my $stripped = Mail::SpamAssassin::Pyzor::Digest::StripHtml::strip($html);
=head1 DESCRIPTION
This module attempts to duplicate pyzor's HTML-stripping logic.
=head1 ACCURACY
This library cannot achieve 100%, bug-for-bug parity with pyzor
because to do so would require duplicating Python's own HTML parsing
library. Since that library's output has changed over time, and those
changes in turn affect pyzor, it's literally impossible to arrive at
a single, fully-compatible reimplementation.
That said, all known divergences between pyzor and this library involve
invalid HTML as input.
Please open bug reports for any divergences you identify, particularly
if the input is valid HTML.
=cut
#----------------------------------------------------------------------
use HTML::Parser ();
our $VERSION = '0.03';
#----------------------------------------------------------------------
=head1 FUNCTIONS
=head2 $stripped = strip( $HTML )
Give it some HTML, and it'll give back the stripped text.
In B<general>, the stripping consists of removing tags as well as
C<E<lt>scriptE<gt>> and C<E<lt>styleE<gt>> elements; however, it also
removes HTML entities.
This tries very hard to duplicate pyzor's behavior with invalid HTML.
=cut
sub strip {
my ($html) = @_;
$html =~ s<\A\s+><>;
$html =~ s<\s+\z><>;
my $p = HTML::Parser->new( api_version => 3 );
my @pieces;
my $accumulate = 1;
$p->handler(
start => sub {
my ($tagname) = @_;
$accumulate = 0 if $tagname eq 'script';
$accumulate = 0 if $tagname eq 'style';
return;
},
'tagname',
);
$p->handler(
end => sub {
$accumulate = 1;
return;
}
);
$p->handler(
text => sub {
my ($copy) = @_;
return if !$accumulate;
# pyzor's HTML parser discards HTML entities. On top of that,
# we need to match, as closely as possible, pyzor's handling of
# invalid HTML entities ... which is a function of Python's
# standard HTML parsing library. This will probably never be
# fully compatible with the pyzor, but we can get it close.
# The original is:
#
# re.compile('&#(?:[0-9]+|[xX][0-9a-fA-F]+)[^0-9a-fA-F]')
#
# The parsing loop then "backs up" one byte if the last
# character isn't a ";". We use a look-ahead assertion to
# mimic that behavior.
$copy =~ s<\&\# (?:[0-9]+ | [xX][0-9a-fA-F]+) (?: ; | \z | (?=[^0-9a-fA-F]) )>< >gx;
# The original is:
#
# re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]')
#
# We again use a look-ahead assertion to mimic Python.
$copy =~ s<\& [a-zA-Z] [-.a-zA-Z0-9]* (?: ; | \z | (?=[^a-zA-Z0-9]) )>< >gx;
# Python's HTMLParser aborts its parsing loop when it encounters
# an invalid numeric reference.
$copy =~ s<\&\#
(?:
[^0-9xX] # anything but the expected first char
|
[0-9]+[a-fA-F] # hex within decimal
|
[xX][^0-9a-fA-F]
)
(.*)
><
( -1 == index($1, ';') ) ? q<> : '&#'
>exs;
# Python's HTMLParser treats invalid entities as incomplete
$copy =~ s<(\&\#?)><$1 >gx;
$copy =~ s<\A\s+><>;
$copy =~ s<\s+\z><>;
push @pieces, \$copy if length $copy;
},
'text,tagname',
);
$p->parse($html);
$p->eof();
my $payload = join( q< >, map { $$_ } @pieces );
# Convert all sequences of whitespace OTHER THAN non-breaking spaces to
# plain spaces.
$payload =~ s<[^\S\x{a0}]+>< >g;
return $payload;
}
1;

View File

@ -201,7 +201,37 @@ sub get_addr_entry {
return $entry unless $email ne '' && (defined $ip || defined $signedby); return $entry unless $email ne '' && (defined $ip || defined $signedby);
my $sql = "SELECT msgcount, totscore FROM $self->{tablename} " . my $sql;
my $sth;
my $rc;
if($self->{main}->{conf}->{txrep_welcomelist_out} and ($email =~ /(?:[^\s\@]+)\@(?:[^\s\@]+)/)) {
$sql = "SELECT msgcount, totscore FROM $self->{tablename} " .
"WHERE username = ? AND email = ? AND ip = 'WELCOMELIST_OUT'";
$sth = $self->{dbh}->prepare($sql);
unless (defined($sth)) {
info("auto-welcomelist: sql-based get_addr_entry %s: SQL prepare error: %s",
"$self->{_username}|$ip", $self->{dbh}->errstr);
}
$rc = $sth->execute($self->{_username}, $email);
my $cnt = 0;
my $aryref;
# 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];
$entry->{totscore} = $aryref->[1];
}
$entry->{exists_p} = 1;
$cnt++;
}
$sth->finish();
return $entry if $rc;
}
undef $sth;
undef $rc;
$sql = "SELECT msgcount, totscore FROM $self->{tablename} " .
"WHERE username = ? AND email = ?"; "WHERE username = ? AND email = ?";
my @args = ( $email ); my @args = ( $email );
if (!$self->{_with_awl_signer}) { if (!$self->{_with_awl_signer}) {
@ -221,7 +251,7 @@ sub get_addr_entry {
} }
$sql .= " ORDER BY last_hit"; $sql .= " ORDER BY last_hit";
my $sth = $self->{dbh}->prepare($sql); $sth = $self->{dbh}->prepare($sql);
unless (defined($sth)) { unless (defined($sth)) {
info("auto-welcomelist: sql-based get_addr_entry %s: SQL prepare error: %s", info("auto-welcomelist: sql-based get_addr_entry %s: SQL prepare error: %s",
@ -229,7 +259,7 @@ sub get_addr_entry {
return $entry; return $entry;
} }
my $rc = $sth->execute($self->{_username}, @args); $rc = $sth->execute($self->{_username}, @args);
if (!$rc) { # there was an error, but try to go on if (!$rc) { # there was an error, but try to go on
info("auto-welcomelist: sql-based get_addr_entry %s: SQL error: %s", info("auto-welcomelist: sql-based get_addr_entry %s: SQL error: %s",
@ -298,7 +328,7 @@ sub add_score {
{ my @fields = qw(username email ip msgcount totscore); { my @fields = qw(username email ip msgcount totscore);
my @signedby; my @signedby;
if ($self->{_with_awl_signer}) { if ($self->{_with_awl_signer} or (defined $signedby and $signedby =~ /^spf\-/) and not ($self->{main}->{conf}->{txrep_welcomelist_out} and ($email =~ /(?:[^\s\@]+)\@(?:[^\s\@]+)/)) ) {
push(@fields, 'signedby'); push(@fields, 'signedby');
@signedby = !defined $signedby ? () : split(' ', lc $signedby); @signedby = !defined $signedby ? () : split(' ', lc $signedby);
@signedby = ( '' ) if !@signedby; @signedby = ( '' ) if !@signedby;
@ -307,7 +337,7 @@ sub add_score {
my $sql = sprintf("INSERT INTO %s (%s) VALUES (%s)", $self->{tablename}, my $sql = sprintf("INSERT INTO %s (%s) VALUES (%s)", $self->{tablename},
join(',', @fields), join(',', ('?') x @fields)); join(',', @fields), join(',', ('?') x @fields));
if ($self->{dsn} =~ /^DBI:(?:pg|SQLite)/i) { if ($self->{dsn} =~ /^DBI:(?:pg|SQLite)/i) {
$sql .= " ON CONFLICT (username, email, signedby, ip) DO UPDATE set msgcount = ?, totscore = totscore + ?"; $sql .= " ON CONFLICT (username, email, signedby, ip) DO UPDATE set msgcount = ?, totscore = " . $self->{tablename} . ".totscore + ?";
} elsif ($self->{dsn} =~ /^DBI:(?:mysql|MariaDB)/i) { } elsif ($self->{dsn} =~ /^DBI:(?:mysql|MariaDB)/i) {
$sql .= " ON DUPLICATE KEY UPDATE msgcount = ?, totscore = totscore + ?"; $sql .= " ON DUPLICATE KEY UPDATE msgcount = ?, totscore = totscore + ?";
} }
@ -320,7 +350,7 @@ sub add_score {
return $entry; return $entry;
} }
if (!$self->{_with_awl_signer}) { if (!$self->{_with_awl_signer} && !(defined $signedby and $signedby =~ /^spf\-/)) {
my $rc; my $rc;
if ($self->{dsn} =~ /^DBI:(?:pg|SQLite|mysql|MariaDB)/i) { if ($self->{dsn} =~ /^DBI:(?:pg|SQLite|mysql|MariaDB)/i) {
$rc = $sth->execute(@args, $entry->{msgcount}, $score); $rc = $sth->execute(@args, $entry->{msgcount}, $score);

View File

@ -65,18 +65,18 @@ our @ISA = qw();
########################################################################### ###########################################################################
=item my $t = Mail::SpamAssassin::Timeout->new({ ... options ... }); =item my $t = Mail::SpamAssassin::Timeout-E<gt>new({ ... options ... });
Constructor. Options include: Constructor. Options include:
=over 4 =over 4
=item secs => $seconds =item secs =E<gt> $seconds
time interval, in seconds. Optional; if neither C<secs> nor C<deadline> is time interval, in seconds. Optional; if neither C<secs> nor C<deadline> is
specified, no timeouts will be applied. specified, no timeouts will be applied.
=item deadline => $unix_timestamp =item deadline =E<gt> $unix_timestamp
Unix timestamp (seconds since epoch) when a timeout is reached in the latest. Unix timestamp (seconds since epoch) when a timeout is reached in the latest.
Optional; if neither B<secs> nor B<deadline> is specified, no timeouts will Optional; if neither B<secs> nor B<deadline> is specified, no timeouts will
@ -108,7 +108,7 @@ sub new {
########################################################################### ###########################################################################
=item $t->run($coderef) =item $t-E<gt>run($coderef)
Run a code reference within the currently-defined timeout. Run a code reference within the currently-defined timeout.
@ -116,7 +116,7 @@ The timeout is as defined by the B<secs> and B<deadline> parameters
to the constructor. to the constructor.
Returns whatever the subroutine returns, or C<undef> on timeout. Returns whatever the subroutine returns, or C<undef> on timeout.
If the timer times out, C<$t-<gt>timed_out()> will return C<1>. If the timer times out, C<$t-E<gt>timed_out()> will return C<1>.
Time elapsed is not cumulative; multiple runs of C<run> will restart the Time elapsed is not cumulative; multiple runs of C<run> will restart the
timeout from scratch. On the other hand, nested timers do observe outer timeout from scratch. On the other hand, nested timers do observe outer
@ -125,9 +125,9 @@ established them, i.e. code running under an inner timer can not exceed
the time limit established by an outer timer. When restarting an outer the time limit established by an outer timer. When restarting an outer
timer on return, elapsed time of a running code is taken into account. timer on return, elapsed time of a running code is taken into account.
=item $t->run_and_catch($coderef) =item $t-E<gt>run_and_catch($coderef)
Run a code reference, as per C<$t-<gt>run()>, but also catching any Run a code reference, as per C<$t-E<gt>run()>, but also catching any
C<die()> calls within the code reference. C<die()> calls within the code reference.
Returns C<undef> if no C<die()> call was executed and C<$@> was unset, or the Returns C<undef> if no C<die()> call was executed and C<$@> was unset, or the
@ -291,7 +291,7 @@ sub _run { # private
########################################################################### ###########################################################################
=item $t->timed_out() =item $t-E<gt>timed_out()
Returns C<1> if the most recent code executed in C<run()> timed out, or Returns C<1> if the most recent code executed in C<run()> timed out, or
C<undef> if it did not. C<undef> if it did not.
@ -305,7 +305,7 @@ sub timed_out {
########################################################################### ###########################################################################
=item $t->reset() =item $t-E<gt>reset()
If called within a C<run()> code reference, causes the current alarm timer If called within a C<run()> code reference, causes the current alarm timer
to be restored to its original setting (useful after our alarm setting was to be restored to its original setting (useful after our alarm setting was

View File

@ -60,7 +60,7 @@ our @EXPORT_OK = qw(&local_tz &base64_decode &base64_encode &base32_encode
&parse_rfc822_date &idn_to_ascii &is_valid_utf_8 &parse_rfc822_date &idn_to_ascii &is_valid_utf_8
&get_user_groups &compile_regexp &qr_to_string &get_user_groups &compile_regexp &qr_to_string
&is_fqdn_valid &parse_header_addresses &force_die &is_fqdn_valid &parse_header_addresses &force_die
&domain_to_search_list); &domain_to_search_list &get_part_details);
our $AM_TAINTED; our $AM_TAINTED;
@ -1392,7 +1392,7 @@ Touch or create a file.
Possible args: Possible args:
create_exclusive => 1 create_exclusive =E<gt> 1
Create a new empty file safely, only if not existing before Create a new empty file safely, only if not existing before
=cut =cut
@ -1936,10 +1936,32 @@ sub setuid_to_euid {
# helper app command-line open # helper app command-line open
sub helper_app_pipe_open { sub helper_app_pipe_open {
my @cmdline;
my $startquote = 0;
my $ntok;
foreach my $tok ( @_ ) {
if(defined $tok && ($tok =~ /^\"/) && ($tok !~ /\"$/)) {
$startquote = 1;
}
if($startquote) {
$ntok .= " " if($tok !~ /^\"/);
$ntok =~ s/\"// if defined $ntok;
$ntok .= $tok;
}
if($startquote && defined $tok && ($tok =~ /\"$/)) {
$startquote = 0;
$ntok =~ s/\"// if defined $ntok;
push(@cmdline, $ntok);
undef $ntok;
} elsif(not $startquote) {
push(@cmdline, $tok);
}
}
if (RUNNING_ON_WINDOWS) { if (RUNNING_ON_WINDOWS) {
return helper_app_pipe_open_windows (@_); return helper_app_pipe_open_windows (@cmdline);
} else { } else {
return helper_app_pipe_open_unix (@_); return helper_app_pipe_open_unix (@cmdline);
} }
} }
@ -2557,6 +2579,82 @@ sub parse_header_addresses {
return @results; return @results;
} }
sub get_part_details {
my ($pms, $part, $prefer_contentdisposition) = @_;
#https://en.wikipedia.org/wiki/MIME#Content-Disposition
#https://github.com/mikel/mail/pull/464
my $ctt = $part->get_header('content-type');
return undef unless defined $ctt; ## no critic (ProhibitExplicitReturnUndef)
my $cte = lc($part->get_header('content-transfer-encoding') || '');
return undef unless ($cte =~ /^(?:base64|quoted\-printable)$/); ## no critic (ProhibitExplicitReturnUndef)
$ctt = _decode_part_header($part, $ctt || '');
my $name = '';
my $cttname = '';
my $ctdname = '';
if ($ctt =~ m/name\s*=\s*["']?([^"';]*)/is) {
$cttname = $1;
$cttname =~ s/\s+$//;
}
my $ctd = $part->get_header('content-disposition');
$ctd = _decode_part_header($part, $ctd || '');
if ($ctd =~ m/filename\s*=\s*["']?([^"';]*)/is) {
$ctdname = $1;
$ctdname =~ s/\s+$//;
}
if (lc $ctdname eq lc $cttname) {
$name = $ctdname;
} elsif ($ctdname eq '') {
$name = $cttname;
} elsif ($cttname eq '') {
$name = $ctdname;
} else {
if ((defined $ctdname) and $prefer_contentdisposition) {
$name = $ctdname;
} else {
$name = $cttname;
}
}
return $ctt, $ctd, $cte, $name;
}
sub _decode_part_header {
my($part, $header_field_body) = @_;
return '' unless defined $header_field_body && $header_field_body ne '';
# deal with folding and cream the newlines and such
$header_field_body =~ s/\n[ \t]+/\n /g;
$header_field_body =~ s/\015?\012//gs;
local($1,$2,$3);
# Multiple encoded sections must ignore the interim whitespace.
# To avoid possible FPs with (\s+(?==\?))?, look for the whole RE
# separated by whitespace.
1 while $header_field_body =~
s{ ( = \? [A-Za-z0-9_-]+ \? [bqBQ] \? [^?]* \? = ) \s+
( = \? [A-Za-z0-9_-]+ \? [bqBQ] \? [^?]* \? = ) }
{$1$2}xsg;
# transcode properly encoded RFC 2047 substrings into UTF-8 octets,
# leave everything else unchanged as it is supposed to be UTF-8 (RFC 6532)
# or plain US-ASCII
$header_field_body =~
s{ (?: = \? ([A-Za-z0-9_-]+) \? ([bqBQ]) \? ([^?]*) \? = ) }
{ $part->__decode_header($1, uc($2), $3) }xsge;
return $header_field_body;
}
# Check some basic parsing mistakes # Check some basic parsing mistakes
sub _valid_parsed_address { sub _valid_parsed_address {
return 0 if !defined $_[0]; return 0 if !defined $_[0];

View File

@ -53,7 +53,7 @@ our @MODULES = (
}, },
{ {
module => 'Net::DNS', module => 'Net::DNS',
version => '0.69', version => '1.10',
desc => 'Used for all DNS-based tests (SBL, XBL, SpamCop, DSBL, etc.), desc => 'Used for all DNS-based tests (SBL, XBL, SpamCop, DSBL, etc.),
perform MX checks, and is also used when manually reporting spam to perform MX checks, and is also used when manually reporting spam to
SpamCop.', SpamCop.',
@ -251,6 +251,12 @@ our @OPTIONAL_MODULES = (
backend for the DBI module, this is the DBD driver required. Version 1.59_01 backend for the DBI module, this is the DBD driver required. Version 1.59_01
or later is needed to provide SQLite 3.25.0 or later.', or later is needed to provide SQLite 3.25.0 or later.',
}, },
{
module => 'LWP::Protocol::https',
version => 0,
desc => 'The "sa-update" program can use this module to make HTTPS requests.
Also used by DecodeShortURLs plugin.',
},
{ {
module => 'LWP::UserAgent', module => 'LWP::UserAgent',
version => 0, version => 0,
@ -323,6 +329,18 @@ our @OPTIONAL_MODULES = (
version => 0, version => 0,
desc => 'Mail::DMARC is used by the optional DMARC plugin.', desc => 'Mail::DMARC is used by the optional DMARC plugin.',
}, },
{
module => 'Devel::Cycle',
version => 0,
desc => 'Devel::Cycle is used in make test in tests that will be harmelessly
skipped if it is not available',
},
{
module => 'Text::Diff',
version => 0,
desc => 'Text::Diff is used in make test in tests that will be harmelessly
skipped if it is not available',
},
); );
our @BINARIES = (); our @BINARIES = ();
@ -382,13 +400,28 @@ if ($^O eq 'freebsd') {
}; };
} }
# The numbers being tested only have to be anything more than truncated packets will have
# That allows some flexibility if the exact test records are changed in the future
our @NETWORK_TESTS = (
{
'name' => 'txttcp.spamassassin.org',
'type' => 'TXT',
'min_answers' => 10,
},
{
'name' => 'multihomed.dnsbltest.spamassassin.org',
'type' => 'A',
'min_answers' => 4,
},
);
########################################################################### ###########################################################################
=head1 METHODS =head1 METHODS
=over 4 =over 4
=item $f->debug_diagnostics () =item $f-E<gt>debug_diagnostics ()
Output some diagnostic information, useful for debugging SpamAssassin Output some diagnostic information, useful for debugging SpamAssassin
problems. problems.

View File

@ -59,6 +59,8 @@ Options:
--add-addr-to-welcomelist=addr Add addr to persistent address welcomelist --add-addr-to-welcomelist=addr Add addr to persistent address welcomelist
--add-addr-to-blocklist=addr Add addr to persistent address blocklist --add-addr-to-blocklist=addr Add addr to persistent address blocklist
--remove-addr-from-welcomelist=addr Remove addr from persistent address list --remove-addr-from-welcomelist=addr Remove addr from persistent address list
-u username, --username=username Override username taken from the runtime
environment, used with SQL
-4 --ipv4only, --ipv4-only, --ipv4 Use IPv4, disable use of IPv6 for DNS etc. -4 --ipv4only, --ipv4-only, --ipv4 Use IPv4, disable use of IPv6 for DNS etc.
-6 Use IPv6, disable use of IPv4 where possible -6 Use IPv6, disable use of IPv4 where possible
--progress Print progress bar --progress Print progress bar
@ -219,6 +221,17 @@ Remove the named email address from a persistent address welcomelist. Note that
you must be running C<spamassassin> or C<spamd> with a persistent address you must be running C<spamassassin> or C<spamd> with a persistent address
list plugin enabled for this to work. list plugin enabled for this to work.
=item B<-u> I<username>, B<--username>=I<username>
If specified this username will override the username taken from the runtime
environment. You can use this option to specify users in a virtual user
configuration when using SQL as the Bayes backend.
NOTE: This option will not change to the given I<username>, it will only attempt
to act on behalf of that user. Because of this you will need to have proper
permissions to be able to change files owned by I<username>. In the case of SQL
this generally is not a problem.
=item B< --ipv4only>, B<--ipv4-only>, B<--ipv4> =item B< --ipv4only>, B<--ipv4-only>, B<--ipv4>
Do not use IPv6 for DNS tests. Normally, SpamAssassin will try to detect if Do not use IPv6 for DNS tests. Normally, SpamAssassin will try to detect if
@ -338,11 +351,11 @@ C<Mail::SpamAssassin>
=head1 BUGS =head1 BUGS
See <https://issues.apache.org/SpamAssassin/> See E<lt>https://issues.apache.org/SpamAssassin/E<gt>
=head1 AUTHORS =head1 AUTHORS
The SpamAssassin(tm) Project <https://spamassassin.apache.org/> The SpamAssassin(tm) Project E<lt>https://spamassassin.apache.org/E<gt>
=head1 COPYRIGHT =head1 COPYRIGHT

View File

@ -56,7 +56,7 @@ endif
# wget https://data.iana.org/TLD/tlds-alpha-by-domain.txt -q -O - | grep -i '^xn--' | idn -u | tr '\n' ' ' | fold -w 80 -s | perl -pe 's/\s+$//; s/.*/util_rb_tld \L$_\n/' # wget https://data.iana.org/TLD/tlds-alpha-by-domain.txt -q -O - | grep -i '^xn--' | idn -u | tr '\n' ' ' | fold -w 80 -s | perl -pe 's/\s+$//; s/.*/util_rb_tld \L$_\n/'
if can(Mail::SpamAssassin::Conf::feature_registryboundaries) if can(Mail::SpamAssassin::Conf::feature_registryboundaries)
# Updated 2022-10-18 # Updated 2023-11-17
util_rb_tld xn--11b4c3d xn--1ck2e1b xn--1qqw23a xn--2scrj9c xn--30rr7y xn--3bst00m util_rb_tld xn--11b4c3d xn--1ck2e1b xn--1qqw23a xn--2scrj9c xn--30rr7y xn--3bst00m
util_rb_tld xn--3ds443g xn--3e0b707e xn--3hcrj9c xn--3pxu8k xn--42c2d9a xn--45br5cyl util_rb_tld xn--3ds443g xn--3e0b707e xn--3hcrj9c xn--3pxu8k xn--42c2d9a xn--45br5cyl
util_rb_tld xn--45brj9c xn--45q11c xn--4dbrk0ce xn--4gbrim xn--54b7fta0cc xn--55qw42g util_rb_tld xn--45brj9c xn--45q11c xn--4dbrk0ce xn--4gbrim xn--54b7fta0cc xn--55qw42g
@ -70,130 +70,128 @@ util_rb_tld xn--fiq64b xn--fiqs8s xn--fiqz9s xn--fjq720a xn--flw351e xn--fpcrj9c
util_rb_tld xn--fzc2c9e2c xn--fzys8d69uvgm xn--g2xx48c xn--gckr3f0f xn--gecrj9c xn--gk3at1e util_rb_tld xn--fzc2c9e2c xn--fzys8d69uvgm xn--g2xx48c xn--gckr3f0f xn--gecrj9c xn--gk3at1e
util_rb_tld xn--h2breg3eve xn--h2brj9c xn--h2brj9c8c xn--hxt814e xn--i1b6b1a6a2e util_rb_tld xn--h2breg3eve xn--h2brj9c xn--h2brj9c8c xn--hxt814e xn--i1b6b1a6a2e
util_rb_tld xn--imr513n xn--io0a7i xn--j1aef xn--j1amh xn--j6w193g xn--jlq480n2rg util_rb_tld xn--imr513n xn--io0a7i xn--j1aef xn--j1amh xn--j6w193g xn--jlq480n2rg
util_rb_tld xn--jlq61u9w7b xn--jvr189m xn--kcrx77d1x4a xn--kprw13d xn--kpry57d xn--kput3i util_rb_tld xn--jvr189m xn--kcrx77d1x4a xn--kprw13d xn--kpry57d xn--kput3i xn--l1acc
util_rb_tld xn--l1acc xn--lgbbat1ad8j xn--mgb9awbf xn--mgba3a3ejt xn--mgba3a4f16a util_rb_tld xn--lgbbat1ad8j xn--mgb9awbf xn--mgba3a3ejt xn--mgba3a4f16a xn--mgba7c0bbn0a
util_rb_tld xn--mgba7c0bbn0a xn--mgbaakc7dvf xn--mgbaam7a8h xn--mgbab2bd xn--mgbah1a3hjkrd util_rb_tld xn--mgbaakc7dvf xn--mgbaam7a8h xn--mgbab2bd xn--mgbah1a3hjkrd xn--mgbai9azgqp6j
util_rb_tld xn--mgbai9azgqp6j xn--mgbayh7gpa xn--mgbbh1a xn--mgbbh1a71e xn--mgbc0a9azcg util_rb_tld xn--mgbayh7gpa xn--mgbbh1a xn--mgbbh1a71e xn--mgbc0a9azcg xn--mgbca7dzdo
util_rb_tld xn--mgbca7dzdo xn--mgbcpq6gpa1a xn--mgberp4a5d4ar xn--mgbgu82a xn--mgbi4ecexp util_rb_tld xn--mgbcpq6gpa1a xn--mgberp4a5d4ar xn--mgbgu82a xn--mgbi4ecexp xn--mgbpl2fh
util_rb_tld xn--mgbpl2fh xn--mgbt3dhd xn--mgbtx2b xn--mgbx4cd0ab xn--mix891f xn--mk1bu44c util_rb_tld xn--mgbt3dhd xn--mgbtx2b xn--mgbx4cd0ab xn--mix891f xn--mk1bu44c xn--mxtq1m
util_rb_tld xn--mxtq1m xn--ngbc5azd xn--ngbe9e0a xn--ngbrx xn--node xn--nqv7f util_rb_tld xn--ngbc5azd xn--ngbe9e0a xn--ngbrx xn--node xn--nqv7f xn--nqv7fs00ema
util_rb_tld xn--nqv7fs00ema xn--nyqy26a xn--o3cw4h xn--ogbpf8fl xn--otu796d xn--p1acf util_rb_tld xn--nyqy26a xn--o3cw4h xn--ogbpf8fl xn--otu796d xn--p1acf xn--p1ai xn--pgbs0dh
util_rb_tld xn--p1ai xn--pgbs0dh xn--pssy2u xn--q7ce6a xn--q9jyb4c xn--qcka1pmc xn--qxa6a util_rb_tld xn--pssy2u xn--q7ce6a xn--q9jyb4c xn--qcka1pmc xn--qxa6a xn--qxam xn--rhqv96g
util_rb_tld xn--qxam xn--rhqv96g xn--rovu88b xn--rvc1e0am3e xn--s9brj9c xn--ses554g util_rb_tld xn--rovu88b xn--rvc1e0am3e xn--s9brj9c xn--ses554g xn--t60b56a xn--tckwe
util_rb_tld xn--t60b56a xn--tckwe xn--tiq49xqyj xn--unup4y xn--vermgensberater-ctb util_rb_tld xn--tiq49xqyj xn--unup4y xn--vermgensberater-ctb xn--vermgensberatung-pwb
util_rb_tld xn--vermgensberatung-pwb xn--vhquv xn--vuq861b xn--w4r85el8fhu5dnra xn--w4rs40l util_rb_tld xn--vhquv xn--vuq861b xn--w4r85el8fhu5dnra xn--w4rs40l xn--wgbh1c xn--wgbl6a
util_rb_tld xn--wgbh1c xn--wgbl6a xn--xhq521b xn--xkc2al3hye2a xn--xkc2dl3a5ee0h xn--y9a3aq util_rb_tld xn--xhq521b xn--xkc2al3hye2a xn--xkc2dl3a5ee0h xn--y9a3aq xn--yfro4i67o
util_rb_tld xn--yfro4i67o xn--ygbi2ammx xn--zfr164b util_rb_tld xn--ygbi2ammx xn--zfr164b
endif endif
# Standard List # Standard List
# For an up to date list of TLDs that can be pasted into this block, run this command: # For an up to date list of TLDs that can be pasted into this block, run this command:
# wget https://data.iana.org/TLD/tlds-alpha-by-domain.txt -q -O - | tail -n+2 | grep -vi '^xn--' | tr '\n' ' ' | fold -w 80 -s | perl -pe 's/\s+$//; s/.*/util_rb_tld \L$_\n/' # wget https://data.iana.org/TLD/tlds-alpha-by-domain.txt -q -O - | tail -n+2 | grep -vi '^xn--' | tr '\n' ' ' | fold -w 80 -s | perl -pe 's/\s+$//; s/.*/util_rb_tld \L$_\n/'
# Updated 2022-10-18 # Updated 2023-11-17
util_rb_tld aaa aarp abarth abb abbott abbvie abc able abogado abudhabi ac academy util_rb_tld aaa aarp abb abbott abbvie abc able abogado abudhabi ac academy accenture
util_rb_tld accenture accountant accountants aco actor ad adac ads adult ae aeg aero aetna util_rb_tld accountant accountants aco actor ad ads adult ae aeg aero aetna af afl africa
util_rb_tld af afl africa ag agakhan agency ai aig airbus airforce airtel akdn al alfaromeo util_rb_tld ag agakhan agency ai aig airbus airforce airtel akdn al alibaba alipay
util_rb_tld alibaba alipay allfinanz allstate ally alsace alstom am amazon americanexpress util_rb_tld allfinanz allstate ally alsace alstom am amazon americanexpress americanfamily
util_rb_tld americanfamily amex amfam amica amsterdam analytics android anquan anz ao aol util_rb_tld amex amfam amica amsterdam analytics android anquan anz ao aol apartments app
util_rb_tld apartments app apple aq aquarelle ar arab aramco archi army arpa art arte as util_rb_tld apple aq aquarelle ar arab aramco archi army arpa art arte as asda asia
util_rb_tld asda asia associates at athleta attorney au auction audi audible audio auspost util_rb_tld associates at athleta attorney au auction audi audible audio auspost author
util_rb_tld author auto autos avianca aw aws ax axa az azure ba baby baidu banamex util_rb_tld auto autos avianca aw aws ax axa az azure ba baby baidu banamex bananarepublic
util_rb_tld bananarepublic band bank bar barcelona barclaycard barclays barefoot bargains util_rb_tld band bank bar barcelona barclaycard barclays barefoot bargains baseball
util_rb_tld baseball basketball bauhaus bayern bb bbc bbt bbva bcg bcn bd be beats beauty util_rb_tld basketball bauhaus bayern bb bbc bbt bbva bcg bcn bd be beats beauty beer
util_rb_tld beer bentley berlin best bestbuy bet bf bg bh bharti bi bible bid bike bing util_rb_tld bentley berlin best bestbuy bet bf bg bh bharti bi bible bid bike bing bingo
util_rb_tld bingo bio biz bj black blackfriday blockbuster blog bloomberg blue bm bms bmw util_rb_tld bio biz bj black blackfriday blockbuster blog bloomberg blue bm bms bmw bn
util_rb_tld bn bnpparibas bo boats boehringer bofa bom bond boo book booking bosch bostik util_rb_tld bnpparibas bo boats boehringer bofa bom bond boo book booking bosch bostik
util_rb_tld boston bot boutique box br bradesco bridgestone broadway broker brother util_rb_tld boston bot boutique box br bradesco bridgestone broadway broker brother
util_rb_tld brussels bs bt build builders business buy buzz bv bw by bz bzh ca cab cafe cal util_rb_tld brussels bs bt build builders business buy buzz bv bw by bz bzh ca cab cafe cal
util_rb_tld call calvinklein cam camera camp canon capetown capital capitalone car caravan util_rb_tld call calvinklein cam camera camp canon capetown capital capitalone car caravan
util_rb_tld cards care career careers cars casa case cash casino cat catering catholic cba util_rb_tld cards care career careers cars casa case cash casino cat catering catholic cba
util_rb_tld cbn cbre cbs cc cd center ceo cern cf cfa cfd cg ch chanel channel charity util_rb_tld cbn cbre cc cd center ceo cern cf cfa cfd cg ch chanel channel charity chase
util_rb_tld chase chat cheap chintai christmas chrome church ci cipriani circle cisco util_rb_tld chat cheap chintai christmas chrome church ci cipriani circle cisco citadel
util_rb_tld citadel citi citic city cityeats ck cl claims cleaning click clinic clinique util_rb_tld citi citic city ck cl claims cleaning click clinic clinique clothing cloud club
util_rb_tld clothing cloud club clubmed cm cn co coach codes coffee college cologne com util_rb_tld clubmed cm cn co coach codes coffee college cologne com comcast commbank
util_rb_tld comcast commbank community company compare computer comsec condos construction util_rb_tld community company compare computer comsec condos construction consulting
util_rb_tld consulting contact contractors cooking cookingchannel cool coop corsica country util_rb_tld contact contractors cooking cool coop corsica country coupon coupons courses
util_rb_tld coupon coupons courses cpa cr credit creditcard creditunion cricket crown crs util_rb_tld cpa cr credit creditcard creditunion cricket crown crs cruise cruises cu
util_rb_tld cruise cruises cu cuisinella cv cw cx cy cymru cyou cz dabur dad dance data util_rb_tld cuisinella cv cw cx cy cymru cyou cz dabur dad dance data date dating datsun
util_rb_tld date dating datsun day dclk dds de deal dealer deals degree delivery dell util_rb_tld day dclk dds de deal dealer deals degree delivery dell deloitte delta democrat
util_rb_tld deloitte delta democrat dental dentist desi design dev dhl diamonds diet util_rb_tld dental dentist desi design dev dhl diamonds diet digital direct directory
util_rb_tld digital direct directory discount discover dish diy dj dk dm dnp do docs doctor util_rb_tld discount discover dish diy dj dk dm dnp do docs doctor dog domains dot download
util_rb_tld dog domains dot download drive dtv dubai dunlop dupont durban dvag dvr dz earth util_rb_tld drive dtv dubai dunlop dupont durban dvag dvr dz earth eat ec eco edeka edu
util_rb_tld eat ec eco edeka edu education ee eg email emerck energy engineer engineering util_rb_tld education ee eg email emerck energy engineer engineering enterprises epson
util_rb_tld enterprises epson equipment er ericsson erni es esq estate et etisalat eu util_rb_tld equipment er ericsson erni es esq estate et etisalat eu eurovision eus events
util_rb_tld eurovision eus events exchange expert exposed express extraspace fage fail util_rb_tld exchange expert exposed express extraspace fage fail fairwinds faith family fan
util_rb_tld fairwinds faith family fan fans farm farmers fashion fast fedex feedback util_rb_tld fans farm farmers fashion fast fedex feedback ferrari ferrero fi fidelity fido
util_rb_tld ferrari ferrero fi fiat fidelity fido film final finance financial fire util_rb_tld film final finance financial fire firestone firmdale fish fishing fit fitness
util_rb_tld firestone firmdale fish fishing fit fitness fj fk flickr flights flir florist util_rb_tld fj fk flickr flights flir florist flowers fly fm fo foo food football ford
util_rb_tld flowers fly fm fo foo food foodnetwork football ford forex forsale forum util_rb_tld forex forsale forum foundation fox fr free fresenius frl frogans frontier ftr
util_rb_tld foundation fox fr free fresenius frl frogans frontdoor frontier ftr fujitsu fun util_rb_tld fujitsu fun fund furniture futbol fyi ga gal gallery gallo gallup game games
util_rb_tld fund furniture futbol fyi ga gal gallery gallo gallup game games gap garden gay util_rb_tld gap garden gay gb gbiz gd gdn ge gea gent genting george gf gg ggee gh gi gift
util_rb_tld gb gbiz gd gdn ge gea gent genting george gf gg ggee gh gi gift gifts gives util_rb_tld gifts gives giving gl glass gle global globo gm gmail gmbh gmo gmx gn godaddy
util_rb_tld giving gl glass gle global globo gm gmail gmbh gmo gmx gn godaddy gold util_rb_tld gold goldpoint golf goo goodyear goog google gop got gov gp gq gr grainger
util_rb_tld goldpoint golf goo goodyear goog google gop got gov gp gq gr grainger graphics util_rb_tld graphics gratis green gripe grocery group gs gt gu guardian gucci guge guide
util_rb_tld gratis green gripe grocery group gs gt gu guardian gucci guge guide guitars util_rb_tld guitars guru gw gy hair hamburg hangout haus hbo hdfc hdfcbank health
util_rb_tld guru gw gy hair hamburg hangout haus hbo hdfc hdfcbank health healthcare help util_rb_tld healthcare help helsinki here hermes hiphop hisamitsu hitachi hiv hk hkt hm hn
util_rb_tld helsinki here hermes hgtv hiphop hisamitsu hitachi hiv hk hkt hm hn hockey util_rb_tld hockey holdings holiday homedepot homegoods homes homesense honda horse
util_rb_tld holdings holiday homedepot homegoods homes homesense honda horse hospital host util_rb_tld hospital host hosting hot hotels hotmail house how hr hsbc ht hu hughes hyatt
util_rb_tld hosting hot hoteles hotels hotmail house how hr hsbc ht hu hughes hyatt hyundai util_rb_tld hyundai ibm icbc ice icu id ie ieee ifm ikano il im imamat imdb immo immobilien
util_rb_tld ibm icbc ice icu id ie ieee ifm ikano il im imamat imdb immo immobilien in inc util_rb_tld in inc industries infiniti info ing ink institute insurance insure int
util_rb_tld industries infiniti info ing ink institute insurance insure int international util_rb_tld international intuit investments io ipiranga iq ir irish is ismaili ist
util_rb_tld intuit investments io ipiranga iq ir irish is ismaili ist istanbul it itau itv util_rb_tld istanbul it itau itv jaguar java jcb je jeep jetzt jewelry jio jll jm jmp jnj
util_rb_tld jaguar java jcb je jeep jetzt jewelry jio jll jm jmp jnj jo jobs joburg jot joy util_rb_tld jo jobs joburg jot joy jp jpmorgan jprs juegos juniper kaufen kddi ke
util_rb_tld jp jpmorgan jprs juegos juniper kaufen kddi ke kerryhotels kerrylogistics util_rb_tld kerryhotels kerrylogistics kerryproperties kfh kg kh ki kia kids kim kindle
util_rb_tld kerryproperties kfh kg kh ki kia kids kim kinder kindle kitchen kiwi km kn util_rb_tld kitchen kiwi km kn koeln komatsu kosher kp kpmg kpn kr krd kred kuokgroup kw ky
util_rb_tld koeln komatsu kosher kp kpmg kpn kr krd kred kuokgroup kw ky kyoto kz la util_rb_tld kyoto kz la lacaixa lamborghini lamer lancaster land landrover lanxess lasalle
util_rb_tld lacaixa lamborghini lamer lancaster lancia land landrover lanxess lasalle lat util_rb_tld lat latino latrobe law lawyer lb lc lds lease leclerc lefrak legal lego lexus
util_rb_tld latino latrobe law lawyer lb lc lds lease leclerc lefrak legal lego lexus lgbt util_rb_tld lgbt li lidl life lifeinsurance lifestyle lighting like lilly limited limo
util_rb_tld li lidl life lifeinsurance lifestyle lighting like lilly limited limo lincoln util_rb_tld lincoln link lipsy live living lk llc llp loan loans locker locus lol london
util_rb_tld linde link lipsy live living lk llc llp loan loans locker locus loft lol london
util_rb_tld lotte lotto love lpl lplfinancial lr ls lt ltd ltda lu lundbeck luxe luxury lv util_rb_tld lotte lotto love lpl lplfinancial lr ls lt ltd ltda lu lundbeck luxe luxury lv
util_rb_tld ly ma macys madrid maif maison makeup man management mango map market marketing util_rb_tld ly ma madrid maif maison makeup man management mango map market marketing
util_rb_tld markets marriott marshalls maserati mattel mba mc mckinsey md me med media meet util_rb_tld markets marriott marshalls mattel mba mc mckinsey md me med media meet
util_rb_tld melbourne meme memorial men menu merckmsd mg mh miami microsoft mil mini mint util_rb_tld melbourne meme memorial men menu merckmsd mg mh miami microsoft mil mini mint
util_rb_tld mit mitsubishi mk ml mlb mls mm mma mn mo mobi mobile moda moe moi mom monash util_rb_tld mit mitsubishi mk ml mlb mls mm mma mn mo mobi mobile moda moe moi mom monash
util_rb_tld money monster mormon mortgage moscow moto motorcycles mov movie mp mq mr ms msd util_rb_tld money monster mormon mortgage moscow moto motorcycles mov movie mp mq mr ms msd
util_rb_tld mt mtn mtr mu museum music mutual mv mw mx my mz na nab nagoya name natura navy util_rb_tld mt mtn mtr mu museum music mv mw mx my mz na nab nagoya name natura navy nba nc
util_rb_tld nba nc ne nec net netbank netflix network neustar new news next nextdirect util_rb_tld ne nec net netbank netflix network neustar new news next nextdirect nexus nf
util_rb_tld nexus nf nfl ng ngo nhk ni nico nike nikon ninja nissan nissay nl no nokia util_rb_tld nfl ng ngo nhk ni nico nike nikon ninja nissan nissay nl no nokia norton now
util_rb_tld northwesternmutual norton now nowruz nowtv np nr nra nrw ntt nu nyc nz obi util_rb_tld nowruz nowtv np nr nra nrw ntt nu nyc nz obi observer office okinawa olayan
util_rb_tld observer office okinawa olayan olayangroup oldnavy ollo om omega one ong onl util_rb_tld olayangroup oldnavy ollo om omega one ong onl online ooo open oracle orange org
util_rb_tld online ooo open oracle orange org organic origins osaka otsuka ott ovh pa page util_rb_tld organic origins osaka otsuka ott ovh pa page panasonic paris pars partners
util_rb_tld panasonic paris pars partners parts party passagens pay pccw pe pet pf pfizer util_rb_tld parts party pay pccw pe pet pf pfizer pg ph pharmacy phd philips phone photo
util_rb_tld pg ph pharmacy phd philips phone photo photography photos physio pics pictet util_rb_tld photography photos physio pics pictet pictures pid pin ping pink pioneer pizza
util_rb_tld pictures pid pin ping pink pioneer pizza pk pl place play playstation plumbing util_rb_tld pk pl place play playstation plumbing plus pm pn pnc pohl poker politie porn
util_rb_tld plus pm pn pnc pohl poker politie porn post pr pramerica praxi press prime pro util_rb_tld post pr pramerica praxi press prime pro prod productions prof progressive promo
util_rb_tld prod productions prof progressive promo properties property protection pru util_rb_tld properties property protection pru prudential ps pt pub pw pwc py qa qpon
util_rb_tld prudential ps pt pub pw pwc py qa qpon quebec quest racing radio re read util_rb_tld quebec quest racing radio re read realestate realtor realty recipes red
util_rb_tld realestate realtor realty recipes red redstone redumbrella rehab reise reisen util_rb_tld redstone redumbrella rehab reise reisen reit reliance ren rent rentals repair
util_rb_tld reit reliance ren rent rentals repair report republican rest restaurant review util_rb_tld report republican rest restaurant review reviews rexroth rich richardli ricoh
util_rb_tld reviews rexroth rich richardli ricoh ril rio rip ro rocher rocks rodeo rogers util_rb_tld ril rio rip ro rocks rodeo rogers room rs rsvp ru rugby ruhr run rw rwe ryukyu
util_rb_tld room rs rsvp ru rugby ruhr run rw rwe ryukyu sa saarland safe safety sakura util_rb_tld sa saarland safe safety sakura sale salon samsclub samsung sandvik
util_rb_tld sale salon samsclub samsung sandvik sandvikcoromant sanofi sap sarl sas save util_rb_tld sandvikcoromant sanofi sap sarl sas save saxo sb sbi sbs sc sca scb schaeffler
util_rb_tld saxo sb sbi sbs sc sca scb schaeffler schmidt scholarships school schule util_rb_tld schmidt scholarships school schule schwarz science scot sd se search seat
util_rb_tld schwarz science scot sd se search seat secure security seek select sener util_rb_tld secure security seek select sener services seven sew sex sexy sfr sg sh
util_rb_tld services ses seven sew sex sexy sfr sg sh shangrila sharp shaw shell shia util_rb_tld shangrila sharp shaw shell shia shiksha shoes shop shopping shouji show si silk
util_rb_tld shiksha shoes shop shopping shouji show showtime si silk sina singles site sj util_rb_tld sina singles site sj sk ski skin sky skype sl sling sm smart smile sn sncf so
util_rb_tld sk ski skin sky skype sl sling sm smart smile sn sncf so soccer social softbank util_rb_tld soccer social softbank software sohu solar solutions song sony soy spa space
util_rb_tld software sohu solar solutions song sony soy spa space sport spot sr srl ss st util_rb_tld sport spot sr srl ss st stada staples star statebank statefarm stc stcgroup
util_rb_tld stada staples star statebank statefarm stc stcgroup stockholm storage store util_rb_tld stockholm storage store stream studio study style su sucks supplies supply
util_rb_tld stream studio study style su sucks supplies supply support surf surgery suzuki util_rb_tld support surf surgery suzuki sv swatch swiss sx sy sydney systems sz tab taipei
util_rb_tld sv swatch swiss sx sy sydney systems sz tab taipei talk taobao target util_rb_tld talk taobao target tatamotors tatar tattoo tax taxi tc tci td tdk team tech
util_rb_tld tatamotors tatar tattoo tax taxi tc tci td tdk team tech technology tel temasek util_rb_tld technology tel temasek tennis teva tf tg th thd theater theatre tiaa tickets
util_rb_tld tennis teva tf tg th thd theater theatre tiaa tickets tienda tiffany tips tires util_rb_tld tienda tips tires tirol tj tjmaxx tjx tk tkmaxx tl tm tmall tn to today tokyo
util_rb_tld tirol tj tjmaxx tjx tk tkmaxx tl tm tmall tn to today tokyo tools top toray util_rb_tld tools top toray toshiba total tours town toyota toys tr trade trading training
util_rb_tld toshiba total tours town toyota toys tr trade trading training travel util_rb_tld travel travelers travelersinsurance trust trv tt tube tui tunes tushu tv tvs tw
util_rb_tld travelchannel travelers travelersinsurance trust trv tt tube tui tunes tushu tv util_rb_tld tz ua ubank ubs ug uk unicom university uno uol ups us uy uz va vacations vana
util_rb_tld tvs tw tz ua ubank ubs ug uk unicom university uno uol ups us uy uz va util_rb_tld vanguard vc ve vegas ventures verisign versicherung vet vg vi viajes video vig
util_rb_tld vacations vana vanguard vc ve vegas ventures verisign versicherung vet vg vi util_rb_tld viking villas vin vip virgin visa vision viva vivo vlaanderen vn vodka
util_rb_tld viajes video vig viking villas vin vip virgin visa vision viva vivo vlaanderen util_rb_tld volkswagen volvo vote voting voto voyage vu wales walmart walter wang wanggou
util_rb_tld vn vodka volkswagen volvo vote voting voto voyage vu vuelos wales walmart util_rb_tld watch watches weather weatherchannel webcam weber website wed wedding weibo
util_rb_tld walter wang wanggou watch watches weather weatherchannel webcam weber website util_rb_tld weir wf whoswho wien wiki williamhill win windows wine winners wme
util_rb_tld wed wedding weibo weir wf whoswho wien wiki williamhill win windows wine util_rb_tld wolterskluwer woodside work works world wow ws wtc wtf xbox xerox xfinity
util_rb_tld winners wme wolterskluwer woodside work works world wow ws wtc wtf xbox xerox util_rb_tld xihuan xin xxx xyz yachts yahoo yamaxun yandex ye yodobashi yoga yokohama you
util_rb_tld xfinity xihuan xin xxx xyz yachts yahoo yamaxun yandex ye yodobashi yoga util_rb_tld youtube yt yun za zappos zara zero zip zm zone zuerich zw
util_rb_tld yokohama you youtube yt yun za zappos zara zero zip zm zone zuerich zw
# #
# 2nd level TLD list # 2nd level TLD list
@ -495,9 +493,17 @@ util_rb_2tld weebly.com
util_rb_2tld ifrance.com util_rb_2tld ifrance.com
util_rb_2tld jimdo.com util_rb_2tld jimdo.com
util_rb_2tld kimsufi.com util_rb_2tld kimsufi.com
util_rb_2tld mail333.su util_rb_2tld azerbaijan.su
util_rb_2tld pisem.su util_rb_2tld east-kazakhstan.su
util_rb_2tld exnet.su
util_rb_2tld georgia.su
util_rb_2tld kalmykia.su
util_rb_2tld mail15.su util_rb_2tld mail15.su
util_rb_2tld mail333.su
util_rb_2tld mangyshlak.su
util_rb_2tld nov.su
util_rb_2tld pisem.su
util_rb_2tld tashkent.su
util_rb_2tld prserv.net util_rb_2tld prserv.net
util_rb_2tld angelfire.com util_rb_2tld angelfire.com
util_rb_2tld 163.to util_rb_2tld 163.to
@ -638,6 +644,8 @@ util_rb_2tld tumblr.com
util_rb_2tld fileave.com util_rb_2tld fileave.com
util_rb_2tld de.tl util_rb_2tld de.tl
util_rb_2tld co.com util_rb_2tld co.com
util_rb_2tld sendpul.se
util_rb_2tld amplifyapp.com
# Dyndns.com # Dyndns.com
util_rb_2tld dyndns-at-home.com util_rb_2tld dyndns-at-home.com
util_rb_2tld dyndns-at-work.com util_rb_2tld dyndns-at-work.com
@ -740,6 +748,7 @@ util_rb_3tld no-ip.co.uk
# #
util_rb_3tld mobile.web.tr util_rb_3tld mobile.web.tr
util_rb_3tld ct.sendgrid.net util_rb_3tld ct.sendgrid.net
util_rb_3tld on.fleek.co
endif endif

View File

@ -1,6 +1,9 @@
# DO NOT EDIT: file generated by build/mkupdates/listpromotable # DO NOT EDIT: file generated by build/mkupdates/listpromotable
# active ruleset list, automatically generated from https://ruleqa.spamassassin.org/ # active ruleset list, automatically generated from https://ruleqa.spamassassin.org/
# with results from: last-net: net-ena-week0 net-ena-week1 net-ena-week2 net-ena-week3 net-ena-week4 net-giovanni-ham net-giovanni-spam net-giovanni-spammy net-grenier net-hege net-jhardin net-llanga net-mmiroslaw-mails-ham net-mmiroslaw-mails-spam net-spamsponge net-thendrikx net-tsz- corpus; day 1: ena-week0 ena-week1 ena-week2 ena-week3 ena-week4 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam spamsponge thendrikx tsz- corpus; day 2: ena-week0 ena-week1 ena-week2 ena-week3 ena-week4 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam spamsponge thendrikx tsz- corpus; day 3: ena-week0 ena-week1 ena-week2 ena-week3 ena-week4 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam spamsponge thendrikx tsz- corpus; day 4: ena-week0 ena-week1 ena-week2 ena-week3 ena-week4 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam spamsponge thendrikx tsz- corpus; day 5: ena-week0 ena-week1 ena-week2 ena-week3 ena-week4 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam spamsponge thendrikx tsz- corpus # with results from: last-net: net-ena-week0 net-ena-week1 net-ena-week2 net-ena-week3 net-ena-week4 net-giovanni-ham net-giovanni-spam net-giovanni-spammy net-grenier net-hege net-jhardin net-llanga net-mmiroslaw-mails-ham net-mmiroslaw-mails-spam net-spamsponge net-tsz-corpus net-whyscream; day 1: ena-week0 ena-week1 ena-week2 ena-week3 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam spamsponge tsz-corpus whyscream; day 2: ena-week0 ena-week1 ena-week2 ena-week3 ena-week4 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam tsz-corpus whyscream; day 3: ena-week0 ena-week1 ena-week2 ena-week3 ena-week4 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam spamsponge tsz-corpus whyscream; day 4: ena-week0 ena-week1 ena-week2 ena-week3 ena-week4 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam spamsponge tsz-corpus whyscream; day 5: ena-week0 ena-week1 ena-week2 ena-week3 ena-week4 giovanni-ham giovanni-spam giovanni-spammy grenier hege jhardin llanga mmiroslaw-mails-ham mmiroslaw-mails-spam spamsponge tsz-corpus whyscream
# good enough
ACCT_PHISHING_MANY
# tflags publish # tflags publish
AC_BR_BONANZA AC_BR_BONANZA
@ -110,6 +113,9 @@ ALL_TRUSTED
# tflags publish # tflags publish
AMAZON_IMG_NOT_RCVD_AMZN AMAZON_IMG_NOT_RCVD_AMZN
# good enough
APOSTROPHE_TOCC
# tflags publish # tflags publish
APP_DEVELOPMENT_FREEM APP_DEVELOPMENT_FREEM
@ -131,9 +137,6 @@ AXB_XMAILER_MIMEOLE_OL_024C2
# good enough # good enough
AXB_X_FF_SEZ_S AXB_X_FF_SEZ_S
# good enough
BAT_BDRY_TO_MALF
# tflags learn # tflags learn
BAYES_00 BAYES_00
@ -188,10 +191,7 @@ BITCOIN_EXTORT_02
# tflags publish # tflags publish
BITCOIN_IMGUR BITCOIN_IMGUR
# good enough # tflags net
BITCOIN_MALF_HTML
# tflags publish
BITCOIN_MALWARE BITCOIN_MALWARE
# tflags publish # tflags publish
@ -242,6 +242,12 @@ BITCOIN_SPAM_12
# tflags net # tflags net
BITCOIN_SPF_ONLYALL BITCOIN_SPF_ONLYALL
# good enough
BITCOIN_TOEQFM
# good enough
BITCOIN_VISTA
# tflags publish # tflags publish
BITCOIN_WFH_01 BITCOIN_WFH_01
@ -251,7 +257,10 @@ BITCOIN_XPRIO
# tflags publish # tflags publish
BITCOIN_YOUR_INFO BITCOIN_YOUR_INFO
# tflags publish # good enough
BODY_SINGLE_URI
# tflags net
BODY_URI_ONLY BODY_URI_ONLY
# tflags publish # tflags publish
@ -392,15 +401,15 @@ DOS_DEREK_AUG08
# good enough # good enough
DOS_OE_TO_MX DOS_OE_TO_MX
# good enough
DOS_OE_TO_MX_IMAGE
# good enough # good enough
DOS_OUTLOOK_TO_MX DOS_OUTLOOK_TO_MX
# tflags publish # tflags publish
DOTGOV_IMAGE DOTGOV_IMAGE
# good enough
DSN_NO_MIMEVERSION
# tflags publish # tflags publish
DX_TEXT_02 DX_TEXT_02
@ -431,6 +440,9 @@ ENV_AND_HDR_SPF_MATCH
# tflags publish # tflags publish
FACEBOOK_IMG_NOT_RCVD_FB FACEBOOK_IMG_NOT_RCVD_FB
# good enough
FAKE_REPLY_C
# tflags publish # tflags publish
FBI_MONEY FBI_MONEY
@ -440,6 +452,9 @@ FBI_SPOOF
# tflags publish # tflags publish
FILL_THIS_FORM FILL_THIS_FORM
# good enough
FILL_THIS_FORM_LOAN
# good enough # good enough
FILL_THIS_FORM_LONG FILL_THIS_FORM_LONG
@ -467,10 +482,10 @@ FONT_INVIS_POSTEXTRAS
# tflags net # tflags net
FORGED_SPF_HELO FORGED_SPF_HELO
# tflags publish # tflags net
FORM_FRAUD FORM_FRAUD
# tflags publish # tflags net
FORM_FRAUD_3 FORM_FRAUD_3
# tflags publish # tflags publish
@ -494,9 +509,6 @@ FREEM_FRNUM_UNICD_EMPTY
# tflags publish # tflags publish
FRNAME_IN_MSG_XPRIO_NO_SUB FRNAME_IN_MSG_XPRIO_NO_SUB
# good enough
FROM_2_EMAILS_SHORT
# tflags publish # tflags publish
FROM_ADDR_WS FROM_ADDR_WS
@ -548,17 +560,23 @@ FROM_MISSP_FREEMAIL
# good enough # good enough
FROM_MISSP_MSFT FROM_MISSP_MSFT
# tflags net # good enough
FROM_MISSP_PHISH
# good enough
FROM_MISSP_REPLYTO FROM_MISSP_REPLYTO
# tflags net # tflags net
FROM_MISSP_SPF_FAIL FROM_MISSP_SPF_FAIL
# good enough
FROM_MISSP_TO_UNDISC
# good enough # good enough
FROM_MISSP_USER FROM_MISSP_USER
# good enough # good enough
FROM_MULTI_NORDNS FROM_MISSP_XPRIO
# tflags net # tflags net
FROM_NEWDOM_BTC FROM_NEWDOM_BTC
@ -581,6 +599,9 @@ FROM_SUSPICIOUS_NTLD
# tflags publish # tflags publish
FROM_SUSPICIOUS_NTLD_FP FROM_SUSPICIOUS_NTLD_FP
# good enough
FROM_UNBAL2
# good enough # good enough
FROM_WSP_TRAIL FROM_WSP_TRAIL
@ -590,6 +611,9 @@ FSL_BULK_SIG
# good enough # good enough
FSL_CTYPE_WIN1251 FSL_CTYPE_WIN1251
# good enough
FSL_HELO_BARE_IP_1
# tflags publish # tflags publish
FSL_NEW_HELO_USER FSL_NEW_HELO_USER
@ -653,21 +677,24 @@ FUZZY_SAVINGS
# tflags publish # tflags publish
FUZZY_SECURITY FUZZY_SECURITY
# tflags publish
FUZZY_TRUSTWALLET
# tflags publish # tflags publish
FUZZY_UNSUBSCRIBE FUZZY_UNSUBSCRIBE
# tflags publish # tflags publish
FUZZY_WALLET FUZZY_WALLET
# tflags publish
FUZZY_WELLSFARGO
# tflags publish # tflags publish
GAPPY_SALES_LEADS_FREEM GAPPY_SALES_LEADS_FREEM
# good enough # good enough
GB_BITCOIN_CP GB_BITCOIN_CP
# good enough
GB_BITCOIN_NH
# tflags publish # tflags publish
GB_CUSTOM_HTM_URI GB_CUSTOM_HTM_URI
@ -689,12 +716,6 @@ GB_GOOGLE_OBFUR
# tflags net # tflags net
GB_HASHBL_BTC GB_HASHBL_BTC
# tflags publish
GB_STORAGE_GOOGLE_EMAIL
# good enough
GB_URI_FLEEK_STO_HTM
# tflags publish # tflags publish
GOOGLE_DOCS_PHISH GOOGLE_DOCS_PHISH
@ -713,9 +734,6 @@ GOOG_MALWARE_DNLD
# tflags publish # tflags publish
GOOG_REDIR_DOCUSIGN GOOG_REDIR_DOCUSIGN
# good enough
GOOG_REDIR_HTML_ONLY
# good enough # good enough
GOOG_REDIR_NORDNS GOOG_REDIR_NORDNS
@ -768,7 +786,7 @@ HEADER_FROM_DIFFERENT_DOMAINS
HEAD_LONG HEAD_LONG
# good enough # good enough
HELO_LOCALHOST HELO_LH_HOME
# tflags publish # tflags publish
HELO_NO_DOMAIN HELO_NO_DOMAIN
@ -783,7 +801,7 @@ HK_CTE_RAW
HK_LOTTO HK_LOTTO
# good enough # good enough
HK_NAME_DRUGS HK_NAME_FM_MR_MRS
# good enough # good enough
HK_NAME_MR_MRS HK_NAME_MR_MRS
@ -803,6 +821,9 @@ HK_RCVD_IP_MULTICAST
# tflags publish # tflags publish
HK_SCAM HK_SCAM
# good enough
HK_WIN
# tflags publish # tflags publish
HOSTED_IMG_DIRECT_MX HOSTED_IMG_DIRECT_MX
@ -818,6 +839,21 @@ HOSTED_IMG_MULTI
# tflags publish # tflags publish
HOSTED_IMG_MULTI_PUB_01 HOSTED_IMG_MULTI_PUB_01
# tflags publish
HREF_EMPTY_NORDNS
# tflags publish
HREF_EMPTY_PHPMAIL
# tflags publish
HREF_EMPTY_XANTIABUSE
# tflags publish
HREF_EMPTY_XAUTHED
# tflags publish
HTML_BADATTR
# tflags userconf # tflags userconf
HTML_CHARSET_FARAWAY HTML_CHARSET_FARAWAY
@ -864,11 +900,14 @@ JH_SPAMMY_PATTERN02
KHOP_HELO_FCRDNS KHOP_HELO_FCRDNS
# good enough # good enough
KHOP_JS_OBFUSCATION KHOP_UNSUB_EMAIL
# tflags publish # tflags publish
LINKEDIN_IMG_NOT_RCVD_LNKN LINKEDIN_IMG_NOT_RCVD_LNKN
# good enough
LIST_PARTIAL_SHORT_MSG
# tflags publish # tflags publish
LIST_PRTL_PUMPDUMP LIST_PRTL_PUMPDUMP
@ -899,6 +938,9 @@ LOTTO_DEPT
# tflags publish # tflags publish
LUCRATIVE LUCRATIVE
# good enough
MALFORMED_FREEMAIL
# tflags publish # tflags publish
MALF_HTML_B64 MALF_HTML_B64
@ -914,8 +956,8 @@ MALW_ATTACH
# tflags publish # tflags publish
MANY_SPAN_IN_TEXT MANY_SPAN_IN_TEXT
# tflags net # good enough
MAY_BE_FORGED MANY_SUBDOM
# tflags publish # tflags publish
MILLION_HUNDRED MILLION_HUNDRED
@ -992,6 +1034,9 @@ MONEY_FROM_41
# good enough # good enough
MONEY_FROM_MISSP MONEY_FROM_MISSP
# good enough
MONEY_NOHTML
# tflags publish # tflags publish
MSGID_DOLLARS_URI_IMG MSGID_DOLLARS_URI_IMG
@ -999,7 +1044,7 @@ MSGID_DOLLARS_URI_IMG
MSGID_HDR_MALF MSGID_HDR_MALF
# good enough # good enough
MSMAIL_PRI_ABNORMAL MSGID_NOFQDN1
# tflags publish # tflags publish
MSM_PRIO_REPTO MSM_PRIO_REPTO
@ -1007,9 +1052,6 @@ MSM_PRIO_REPTO
# good enough # good enough
MSOE_MID_WRONG_CASE MSOE_MID_WRONG_CASE
# good enough
NAME_EMAIL_DIFF
# good enough # good enough
NA_DOLLARS NA_DOLLARS
@ -1055,9 +1097,6 @@ NSL_RCVD_FROM_USER
# good enough # good enough
NSL_RCVD_HELO_USER NSL_RCVD_HELO_USER
# good enough
NUMBERONLY_BITCOIN_EXP
# tflags publish # tflags publish
OBFU_BITCOIN OBFU_BITCOIN
@ -1076,41 +1115,38 @@ ODD_FREEM_REPTO
# good enough # good enough
PDS_BAD_THREAD_QP_64 PDS_BAD_THREAD_QP_64
# good enough
PDS_BRAND_SUBJ_NAKED_TO
# good enough # good enough
PDS_BTC_ID PDS_BTC_ID
# good enough # good enough
PDS_BTC_MSGID PDS_BTC_MSGID
# good enough
PDS_BTC_NTLD
# good enough # good enough
PDS_DBL_URL_TNB_RUNON PDS_DBL_URL_TNB_RUNON
# good enough # good enough
PDS_FROM_2_EMAILS PDS_FRNOM_TODOM_DBL_URL
# good enough
PDS_FRNOM_TODOM_NAKED_TO
# good enough
PDS_FROM_NAME_TO_DOMAIN
# tflags net # tflags net
PDS_HELO_SPF_FAIL PDS_HELO_SPF_FAIL
# good enough # good enough
PDS_NAKED_TO_NUMERO PDS_HP_HELO_NORDNS
# good enough # good enough
PDS_NO_FULL_NAME_SPOOFED_URL PDS_OTHER_BAD_TLD
# good enough # good enough
PDS_RDNS_DYNAMIC_FP PDS_PHP_EVAL
# good enough
PDS_SHORT_SPOOFED_URL
# good enough
PDS_TINYSUBJ_URISHRT
# good enough
PDS_TONAME_EQ_TOLOCAL_FREEM_FORGE
# tflags publish # tflags publish
PHISH_ATTACH PHISH_ATTACH
@ -1127,6 +1163,9 @@ PHP_NOVER_MUA
# tflags publish # tflags publish
PHP_ORIG_SCRIPT PHP_ORIG_SCRIPT
# good enough
PHP_ORIG_SCRIPT_EVAL
# tflags publish # tflags publish
PHP_SCRIPT PHP_SCRIPT
@ -1211,6 +1250,9 @@ RCVD_IN_DNSWL_MED
# tflags net # tflags net
RCVD_IN_DNSWL_NONE RCVD_IN_DNSWL_NONE
# tflags net
RCVD_IN_IADB_COURT
# tflags net # tflags net
RCVD_IN_IADB_DK RCVD_IN_IADB_DK
@ -1232,6 +1274,9 @@ RCVD_IN_IADB_EPIA
# tflags net # tflags net
RCVD_IN_IADB_GOODMAIL RCVD_IN_IADB_GOODMAIL
# tflags net
RCVD_IN_IADB_LEG_MAND
# tflags net # tflags net
RCVD_IN_IADB_LISTED RCVD_IN_IADB_LISTED
@ -1454,6 +1499,9 @@ REPTO_419_FRAUD_YN
# tflags publish # tflags publish
REPTO_INFONUMSCOM REPTO_INFONUMSCOM
# good enough
RISK_FREE
# tflags publish # tflags publish
SCC_BOGUS_CTE_1 SCC_BOGUS_CTE_1
@ -1475,6 +1523,9 @@ SCC_ISEMM_LID_1A
# tflags publish # tflags publish
SCC_ISEMM_LID_1B SCC_ISEMM_LID_1B
# good enough
SCC_SPAMMER_ADDR_2
# tflags publish # tflags publish
SCC_SPECIAL_GUID SCC_SPECIAL_GUID
@ -1487,9 +1538,6 @@ SENDGRID_REDIR_PHISH
# tflags publish # tflags publish
SEO_SUSP_NTLD SEO_SUSP_NTLD
# good enough
SERGIO_SUBJECT_VIAGRA01
# tflags publish # tflags publish
SHOPIFY_IMG_NOT_RCVD_SFY SHOPIFY_IMG_NOT_RCVD_SFY
@ -1502,6 +1550,15 @@ SHORT_IMG_SUSP_NTLD
# good enough # good enough
SHORT_SHORTNER SHORT_SHORTNER
# tflags publish
SHY_OBFU_EXPIRE
# tflags publish
SHY_OBFU_PASSWORD
# tflags publish
SPAM_CWINDOWSNET
# tflags net # tflags net
SPF_FAIL SPF_FAIL
@ -1557,7 +1614,7 @@ STATIC_XPRIO_OLE
STOCK_TIP STOCK_TIP
# good enough # good enough
STOX_BOUND_090909_B STY_INVIS_DIRECT
# tflags userconf # tflags userconf
SUBJECT_IN_BLOCKLIST SUBJECT_IN_BLOCKLIST
@ -1565,6 +1622,9 @@ SUBJECT_IN_BLOCKLIST
# tflags userconf # tflags userconf
SUBJECT_IN_WELCOMELIST SUBJECT_IN_WELCOMELIST
# good enough
SUBJECT_NEEDS_ENCODING
# good enough # good enough
SUBJ_ATTENTION SUBJ_ATTENTION
@ -1574,6 +1634,15 @@ SUBJ_BRKN_WORDNUMS
# tflags net # tflags net
SURBL_BLOCKED SURBL_BLOCKED
# good enough
SUSP_UTF8_WORD_COMBO
# good enough
SUSP_UTF8_WORD_FROM
# good enough
SUSP_UTF8_WORD_MANY
# good enough # good enough
SUSP_UTF8_WORD_SUBJ SUSP_UTF8_WORD_SUBJ
@ -1592,7 +1661,7 @@ TEQF_USR_IMAGE
# tflags publish # tflags publish
TEQF_USR_MSGID_HEX TEQF_USR_MSGID_HEX
# tflags net # tflags publish
TEQF_USR_MSGID_MALF TEQF_USR_MSGID_MALF
# tflags publish # tflags publish
@ -1604,12 +1673,18 @@ THIS_IS_ADV_SUSP_NTLD
# tflags publish # tflags publish
TONLINE_FAKE_DKIM TONLINE_FAKE_DKIM
# good enough
TONOM_EQ_TOLOC_SHRT_SHRTNER
# tflags publish # tflags publish
TO_EQ_FM_DIRECT_MX TO_EQ_FM_DIRECT_MX
# tflags net # tflags net
TO_EQ_FM_DOM_SPF_FAIL TO_EQ_FM_DOM_SPF_FAIL
# tflags net
TO_EQ_FM_HTML_ONLY
# tflags net # tflags net
TO_EQ_FM_SPF_FAIL TO_EQ_FM_SPF_FAIL
@ -1628,7 +1703,7 @@ TO_NO_BRKTS_HTML_IMG
# tflags publish # tflags publish
TO_NO_BRKTS_HTML_ONLY TO_NO_BRKTS_HTML_ONLY
# good enough # tflags net
TO_NO_BRKTS_MSFT TO_NO_BRKTS_MSFT
# tflags publish # tflags publish
@ -1641,17 +1716,29 @@ TO_NO_BRKTS_PCNT
TO_TOO_MANY_WFH_01 TO_TOO_MANY_WFH_01
# good enough # good enough
TVD_PH_BODY_META TT_MSGID_TRUNC
# good enough # good enough
TVD_RCVD_SPACE_BRACKET TVD_DOLLARS_US
# tflags net # good enough
TVD_FROM_1
# good enough
TVD_PH_7
# good enough
TVD_PH_BODY_ACCOUNTS_PRE
# good enough
TVD_SPACE_ENCODED TVD_SPACE_ENCODED
# tflags net # good enough
TVD_SPACE_RATIO_MINFP TVD_SPACE_RATIO_MINFP
# tflags net
TVD_SUBJ_NUM_OBFU_MINFP
# tflags publish # tflags publish
TW_GIBBERISH_MANY TW_GIBBERISH_MANY
@ -1670,6 +1757,9 @@ UNICODE_OBFU_ASC
# tflags publish # tflags publish
UNICODE_OBFU_ZW UNICODE_OBFU_ZW
# tflags publish
UNICODE_OBFU_ZW_MANY
# tflags userconf # tflags userconf
UNPARSEABLE_RELAY UNPARSEABLE_RELAY
@ -1751,9 +1841,6 @@ URIBL_SBL
# tflags net # tflags net
URIBL_SBL_A URIBL_SBL_A
# tflags net
URIBL_WS_SURBL
# tflags net # tflags net
URIBL_ZEN_BLOCKED URIBL_ZEN_BLOCKED
@ -1766,6 +1853,9 @@ URI_ADOBESPARK
# tflags publish # tflags publish
URI_AZURE_CLOUDAPP URI_AZURE_CLOUDAPP
# tflags publish
URI_CLOUDFLAREIPFS
# tflags publish # tflags publish
URI_DASHGOVEDU URI_DASHGOVEDU
@ -1802,6 +1892,9 @@ URI_HOST_IN_BLOCKLIST
# tflags userconf # tflags userconf
URI_HOST_IN_WELCOMELIST URI_HOST_IN_WELCOMELIST
# tflags publish
URI_IMG_CWINDOWSNET
# tflags publish # tflags publish
URI_IMG_WP_REDIR URI_IMG_WP_REDIR
@ -1814,9 +1907,6 @@ URI_MALWARE_SCMS
# tflags userconf # tflags userconf
URI_NOVOWEL URI_NOVOWEL
# good enough
URI_OBFU_DOM
# tflags publish # tflags publish
URI_ONLY_MSGID_MALF URI_ONLY_MSGID_MALF
@ -1844,7 +1934,7 @@ URI_WPADMIN
# tflags publish # tflags publish
URI_WP_DIRINDEX URI_WP_DIRINDEX
# tflags net # tflags publish
URI_WP_HACKED URI_WP_HACKED
# tflags publish # tflags publish
@ -1886,15 +1976,27 @@ USER_IN_WELCOMELIST
# tflags userconf # tflags userconf
USER_IN_WELCOMELIST_TO USER_IN_WELCOMELIST_TO
# good enough
US_DOLLARS_3
# tflags publish # tflags publish
VFY_ACCT_NORDNS VFY_ACCT_NORDNS
# tflags publish
VISTA_COST
# tflags publish
VISTA_TONOM_EQ_TOLOC
# tflags publish # tflags publish
VPS_NO_NTLD VPS_NO_NTLD
# tflags publish # tflags publish
WALMART_IMG_NOT_RCVD_WAL WALMART_IMG_NOT_RCVD_WAL
# good enough
WIKI_IMG
# tflags publish # tflags publish
WORD_INVIS WORD_INVIS
@ -1907,20 +2009,26 @@ XFER_LOTSA_MONEY
# tflags publish # tflags publish
XM_DIGITS_ONLY XM_DIGITS_ONLY
# good enough
XM_LIGHT_HEAVY
# tflags publish # tflags publish
XM_PHPMAILER_FORGED XM_PHPMAILER_FORGED
# tflags publish # tflags publish
XM_RANDOM XM_RANDOM
# good enough
XM_RECPTID
# tflags net # tflags net
XPRIO XPRIO
# tflags publish # tflags publish
XPRIO_SHORT_SUBJ XPRIO_SHORT_SUBJ
# good enough # tflags publish
XPRIO_URL_SHORTNER XPRIO_VISTA
# good enough # good enough
YOUR_DELIVERY_ADDRESS YOUR_DELIVERY_ADDRESS

View File

@ -17,7 +17,7 @@
########################################################################### ###########################################################################
# HashBL - Query hashed/unhashed strings, emails, uris etc from DNS lists # HashBL - Query hashed/unhashed strings, emails, uris etc from DNS lists
# # NOTE: This was enabled by default in v4.0.0
loadplugin Mail::SpamAssassin::Plugin::HashBL loadplugin Mail::SpamAssassin::Plugin::HashBL
# ResourceLimits - assure your spamd child processes # ResourceLimits - assure your spamd child processes

24
upstream/rules/v401.pre Normal file
View File

@ -0,0 +1,24 @@
# This is the right place to customize your installation of SpamAssassin.
#
# See 'perldoc Mail::SpamAssassin::Conf' for details of what can be
# tweaked.
#
# This file was installed during the installation of SpamAssassin 4.0.0,
# and contains plugin loading commands for the new plugins added in that
# release. It will not be overwritten during future SpamAssassin installs,
# so you can modify it to enable some disabled-by-default plugins below,
# if you so wish.
#
# There are now multiple files read to enable plugins in the
# /etc/mail/spamassassin directory; previously only one, "init.pre" was
# read. Now both "init.pre", "v310.pre", and any other files ending in
# ".pre" will be read. As future releases are made, new plugins will be
# added to new files, named according to the release they're added in.
###########################################################################
# AuthRes - use Authentication-Results header fields
#
# This plugin parses Authentication-Results header fields and can supply
# the results obtained to other plugins.
#
# loadplugin Mail::SpamAssassin::Plugin::AuthRes

View File

@ -41,7 +41,7 @@ BEGIN { # see comments in "spamassassin.raw" for doco
sub usage { sub usage {
die " die "
usage: sa-awl [--clean] [--min n] [dbfile] usage: sa-awl [--clean] [--dry-run] [--min n] [dbfile]
"; ";
} }
@ -51,10 +51,11 @@ use POSIX qw(locale_h setsid sigprocmask _exit);
POSIX::setlocale(LC_TIME,'C'); POSIX::setlocale(LC_TIME,'C');
our ( $opt_clean, $opt_min, $opt_help ); our ( $opt_clean, $opt_dryrun, $opt_min, $opt_help );
GetOptions( GetOptions(
'clean' => \$opt_clean, 'clean' => \$opt_clean,
'dry-run' => \$opt_dryrun,
'min:i' => \$opt_min, 'min:i' => \$opt_min,
'help' => \$opt_help 'help' => \$opt_help
) or usage(); ) or usage();
@ -79,7 +80,7 @@ if ($#ARGV == -1) {
} }
my %h; my %h;
if ($opt_clean) { if ($opt_clean and not $opt_dryrun) {
tie %h, "AnyDBM_File",$db, O_RDWR,0600 tie %h, "AnyDBM_File",$db, O_RDWR,0600
or die "Cannot open r/w file $db: $!\n"; or die "Cannot open r/w file $db: $!\n";
} else { } else {
@ -87,11 +88,15 @@ if ($opt_clean) {
or die "Cannot open file $db: $!\n"; or die "Cannot open file $db: $!\n";
} }
if (!$opt_clean) { # just pretend to be cleaning if (not $opt_clean or $opt_dryrun) { # just pretend to be cleaning
while (my($key, $count) = each %h) { while (my($key, $count) = each %h) {
next if $key =~ /totscore$/; next if $key =~ /totscore$/;
next if (($count >= $opt_min) and ($opt_dryrun and $opt_clean));
my $totscore = $h{"$key|totscore"}; my $totscore = $h{"$key|totscore"};
next unless defined($totscore); next unless defined($totscore);
if($opt_dryrun and $opt_clean) {
printf("cleaning [dry-run]: ");
}
printf("%8.1f %15s -- %s\n", printf("%8.1f %15s -- %s\n",
$totscore/$count, sprintf("(%.1f/%d)",$totscore,$count), $key); $totscore/$count, sprintf("(%.1f/%d)",$totscore,$count), $key);
} }
@ -130,7 +135,7 @@ sa-awl - examine and manipulate SpamAssassin's auto-welcomelist db
=head1 SYNOPSIS =head1 SYNOPSIS
B<sa-awl> [--clean] [--min n] [dbfile] B<sa-awl> [--clean] [--dry-run] [--min n] [dbfile]
=head1 DESCRIPTION =head1 DESCRIPTION
@ -148,6 +153,12 @@ The default is C<$HOME/.spamassassin/auto-welcomelist>.
Clean out infrequently-used AWL entries. The C<--min> switch can be Clean out infrequently-used AWL entries. The C<--min> switch can be
used to select the threshold at which entries are kept or deleted. used to select the threshold at which entries are kept or deleted.
=item --dry-run
When specified with th C<--clean> option it displays the infrequently-used AWL entries
that will be deleted. The C<--min> switch can be
used to select the threshold at which entries are kept or deleted.
=item --min n =item --min n
Select the threshold at which entries are kept or deleted when C<--clean> is Select the threshold at which entries are kept or deleted when C<--clean> is

View File

@ -455,7 +455,7 @@ C<Mail::SpamAssassin> version 3.1.1 or higher (3.1.6 or higher recommended)
=head1 AUTHOR =head1 AUTHOR
Daryl C. W. O'Shea, DOS Technologies <spamassassin@dostech.ca> Daryl C. W. O'Shea, DOS Technologies E<lt>spamassassin@dostech.caE<gt>
=head1 COPYRIGHT AND LICENSE =head1 COPYRIGHT AND LICENSE

View File

@ -255,6 +255,7 @@ sub compile_base_strings {
die "secure_tmpdir failed" unless $dirpath && -w $dirpath; die "secure_tmpdir failed" unless $dirpath && -w $dirpath;
my $sudo = ($opt{sudo} ? 'sudo ' : ''); my $sudo = ($opt{sudo} ? 'sudo ' : '');
delete $ENV{'PERL_MM_OPT'}; # bug 8199
foreach my $ruletype (sort keys %{$conf->{base_orig}}) foreach my $ruletype (sort keys %{$conf->{base_orig}})
{ {
@ -803,11 +804,11 @@ C<Mail::SpamAssassin::Plugin::Rule2XSBody>
=head1 BUGS =head1 BUGS
See <https://issues.apache.org/SpamAssassin/> See E<lt>https://issues.apache.org/SpamAssassin/E<gt>
=head1 AUTHORS =head1 AUTHORS
The Apache SpamAssassin(tm) Project <https://spamassassin.apache.org/> The Apache SpamAssassin(tm) Project E<lt>https://spamassassin.apache.org/E<gt>
=head1 LICENSE AND COPYRIGHT =head1 LICENSE AND COPYRIGHT

View File

@ -917,7 +917,7 @@ perform a similar procedure for each one of them.
This will sync any outstanding journal entries This will sync any outstanding journal entries
=item sa-learn --backup > backup.txt =item sa-learn --backup E<gt> backup.txt
This will save all your Bayes data to a plain text file. This will save all your Bayes data to a plain text file.
@ -945,7 +945,7 @@ Again, you need to do this for every database.
=back =back
If you are migrating to SQL you can make use of the -u <username> If you are migrating to SQL you can make use of the -u I<username>
option in sa-learn to populate each user's database. Otherwise, you option in sa-learn to populate each user's database. Otherwise, you
must run sa-learn as the user who database you are restoring. must run sa-learn as the user who database you are restoring.
@ -1238,9 +1238,9 @@ or if all of the following are true (opportunistic):
=item - bayes_auto_expire does not equal 0 =item - bayes_auto_expire does not equal 0
=item - the number of tokens in the DB is > 100,000 =item - the number of tokens in the DB is E<gt> 100,000
=item - the number of tokens in the DB is > bayes_expiry_max_db_size =item - the number of tokens in the DB is E<gt> bayes_expiry_max_db_size
=item - there is at least a 12 hr difference between the oldest and newest token atimes =item - there is at least a 12 hr difference between the oldest and newest token atimes
@ -1276,7 +1276,7 @@ old_atime_delta * old_reduction_count / goal)
=item - estimated new atime delta is < 12 hrs =item - estimated new atime delta is < 12 hrs
=item - the difference between the last reduction count and the goal reduction count is > 50% =item - the difference between the last reduction count and the goal reduction count is E<gt> 50%
=back =back

View File

@ -22,11 +22,11 @@ use warnings;
use re 'taint'; use re 'taint';
my $VERSION = 'svnunknown'; my $VERSION = 'svnunknown';
if ('$Id: sa-update.raw 1900642 2022-05-07 06:01:02Z hege $' =~ ':') { if ('$Id: sa-update.raw 1914735 2023-12-17 09:11:35Z sidney $' =~ ':') {
# Subversion keyword "$Id: sa-update.raw 1900642 2022-05-07 06:01:02Z hege $" has been successfully expanded. # Subversion keyword "$Id: sa-update.raw 1914735 2023-12-17 09:11:35Z sidney $" has been successfully expanded.
# Doesn't happen with automated launchpad builds: # Doesn't happen with automated launchpad builds:
# https://bugs.launchpad.net/launchpad/+bug/780916 # https://bugs.launchpad.net/launchpad/+bug/780916
$VERSION = &Mail::SpamAssassin::Version . ' / svn' . (split(/\s+/, '$Id: sa-update.raw 1900642 2022-05-07 06:01:02Z hege $'))[2]; $VERSION = &Mail::SpamAssassin::Version . ' / svn' . (split(/\s+/, '$Id: sa-update.raw 1914735 2023-12-17 09:11:35Z sidney $'))[2];
} }
my $PREFIX = '@@PREFIX@@'; # substituted at 'make' time my $PREFIX = '@@PREFIX@@'; # substituted at 'make' time
@ -100,6 +100,7 @@ BEGIN {
$have_lwp = eval { $have_lwp = eval {
require LWP::UserAgent; require LWP::UserAgent;
require LWP::Protocol::https;
}; };
if (eval { require IO::Socket::IP }) { # handles IPv6 and IPv4 if (eval { require IO::Socket::IP }) { # handles IPv6 and IPv4
@ -1458,7 +1459,7 @@ sub do_dns_query {
next if !$rr; # no answer records, only rcode next if !$rr; # no answer records, only rcode
next if $rr->type ne $rr_type; next if $rr->type ne $rr_type;
# scalar context! # scalar context!
my $text = $rr->UNIVERSAL::can('txtdata') ? $rr->txtdata : $rr->rdatastr; my $text = $rr->UNIVERSAL::can('txtdata') ? $rr->txtdata : $rr->rdstring;
push(@result,$text) if defined $text && $text ne ''; push(@result,$text) if defined $text && $text ne '';
} }
printf("DNS %s query: %s -> %s\n", $rr_type, $query, join(", ",@result)) printf("DNS %s query: %s -> %s\n", $rr_type, $query, join(", ",@result))
@ -2056,6 +2057,19 @@ configuration, based on channels. The default channel is
I<updates.spamassassin.org>, which has updated rules since the previous I<updates.spamassassin.org>, which has updated rules since the previous
release. release.
NOTE: channel names are domain names, but DO NOT typically have any DNS
records other than (maybe) NS records. There is a tree of records below that
name which denote the SpamAssassin version and resolve that name to the
version number of the latest rules, e.g. to find the latest update
version number for SpamAssassin v4.0.0:
$ host -t txt 0.0.4.updates.spamassassin.org
0.0.4.updates.spamassassin.org is an alias for 3.3.3.updates.spamassassin.org.
3.3.3.updates.spamassassin.org descriptive text "1907730"
That also illuminates the fact that the current ruleset is supposed to be
backward-compatible to v3.3.3.
Update archives are verified using GPG signatures by default. If GPG is Update archives are verified using GPG signatures by default. If GPG is
disabled (not recommended), file integrity is checked with SHA512 or SHA256 disabled (not recommended), file integrity is checked with SHA512 or SHA256
checksums. checksums.
@ -2265,7 +2279,7 @@ Mail::SpamAssassin(3)
Mail::SpamAssassin::Conf(3) Mail::SpamAssassin::Conf(3)
spamassassin(1) spamassassin(1)
spamd(1) spamd(1)
<https://wiki.apache.org/spamassassin/RuleUpdates> E<lt>https://wiki.apache.org/spamassassin/RuleUpdatesE<gt>
=head1 PREREQUISITES =head1 PREREQUISITES
@ -2273,11 +2287,11 @@ C<Mail::SpamAssassin>
=head1 BUGS =head1 BUGS
See <https://issues.apache.org/SpamAssassin/> See E<lt>https://issues.apache.org/SpamAssassin/E<gt>
=head1 AUTHORS =head1 AUTHORS
The Apache SpamAssassin(tm) Project <https://spamassassin.apache.org/> The Apache SpamAssassin(tm) Project E<lt>https://spamassassin.apache.org/E<gt>
=head1 LICENSE AND COPYRIGHT =head1 LICENSE AND COPYRIGHT

View File

@ -182,6 +182,7 @@ GetOptions(
'add-to-welcomelist|W' => \$opt{'add-to-welcomelist'}, 'add-to-welcomelist|W' => \$opt{'add-to-welcomelist'},
'add-to-blacklist' => \$opt{'add-to-blocklist'}, # removed in 4.1 'add-to-blacklist' => \$opt{'add-to-blocklist'}, # removed in 4.1
'add-to-whitelist' => \$opt{'add-to-welcomelist'}, # removed in 4.1 'add-to-whitelist' => \$opt{'add-to-welcomelist'}, # removed in 4.1
'username|u=s' => \$opt{'username'},
'configpath|config-file|config-dir|c|C=s' => \$opt{'configpath'}, 'configpath|config-file|config-dir|c|C=s' => \$opt{'configpath'},
'create-prefs!' => \$opt{'create-prefs'}, 'create-prefs!' => \$opt{'create-prefs'},
'pre=s' => \@{$opt{'pre'}}, 'pre=s' => \@{$opt{'pre'}},
@ -265,6 +266,7 @@ my $spamtest = Mail::SpamAssassin->new(
rules_filename => $opt{'configpath'}, rules_filename => $opt{'configpath'},
site_rules_filename => $opt{'siteconfigpath'}, site_rules_filename => $opt{'siteconfigpath'},
userprefs_filename => $opt{'prefspath'}, userprefs_filename => $opt{'prefspath'},
username => $opt{'username'},
force_ipv4 => $opt{'force_ipv4'}, force_ipv4 => $opt{'force_ipv4'},
force_ipv6 => $opt{'force_ipv6'}, force_ipv6 => $opt{'force_ipv6'},
local_tests_only => $opt{'local'}, local_tests_only => $opt{'local'},
@ -288,7 +290,7 @@ if ($opt{'lint'}) {
# make sure we notice any write errors while flushing output buffer # make sure we notice any write errors while flushing output buffer
close STDOUT or die "error closing STDOUT: $!"; close STDOUT or die "error closing STDOUT: $!";
close STDIN or die "error closing STDIN: $!"; close STDIN or die "error closing STDIN: $!";
exit $res ? 1 : 0; exit($res ? 1 : 0);
} }
if ($opt{'remove-addr-from-welcomelist'} || if ($opt{'remove-addr-from-welcomelist'} ||
@ -894,11 +896,11 @@ from the SpamAssassin distribution.
=head1 BUGS =head1 BUGS
See <https://issues.apache.org/SpamAssassin/> See E<lt>https://issues.apache.org/SpamAssassin/E<gt>
=head1 AUTHORS =head1 AUTHORS
The SpamAssassin(tm) Project <https://spamassassin.apache.org/> The SpamAssassin(tm) Project E<lt>https://spamassassin.apache.org/E<gt>
=head1 COPYRIGHT AND LICENSE =head1 COPYRIGHT AND LICENSE

View File

@ -33,6 +33,10 @@ To build SpamAssassin you must have installed a Windows version of Perl
and the modules that are listed as required in the general SpamAssassin and the modules that are listed as required in the general SpamAssassin
documentation. documentation.
Strawberry Perl provides a C compiler that is enough to build spamc.exe.
If you want to use Microsoft Visual C++ instead, you should download
Microsoft Visual C++ Toolkit from Microsoft website.
Building spamc for Windows has been tested with Microsoft Visual C++ 6.0 Building spamc for Windows has been tested with Microsoft Visual C++ 6.0
and with Microsoft Visual C++ Toolkit 2003. It will probably just work and with Microsoft Visual C++ Toolkit 2003. It will probably just work
with any recent version of VC++. Some installation files would have to with any recent version of VC++. Some installation files would have to
@ -73,7 +77,7 @@ by unpacking the source files into some directory, for example,
/usr/local/src/spamassassin, then in a Cygwin bash shell /usr/local/src/spamassassin, then in a Cygwin bash shell
cd /usr/local/src/spamassassin cd /usr/local/src/spamassassin
perl Makefile.PL perl Makefile.PL BUILD_SPAMC=yes
make make
make test make test
make install make install
@ -91,7 +95,7 @@ Building the Windows version
Unpack the SpamAssassin source tree into a different directory than you Unpack the SpamAssassin source tree into a different directory than you
used for building the Cygwin version, for example C:\spamassassin\. used for building the Cygwin version, for example C:\spamassassin\.
Start up a Widows command shell. In the shell, set the environment Start up a Windows command shell. In the shell, set the environment
variable SPAMD_HOST to be the host name or ip address of the spamd variable SPAMD_HOST to be the host name or ip address of the spamd
server to be used for testing. In this case you would use the command server to be used for testing. In this case you would use the command
@ -102,9 +106,11 @@ environment variable SPAMD_PORT. To use SSL during the test,
set SC_ARGS=-S set SC_ARGS=-S
Make sure that the environment is set up for running VC++. In VC++ 6.0 If you want to build spamc using Microsoft VC++, make sure that
there is a batch file created during installation that sets the the environment is set up for running VC++.
environment. In a typical installation that would be found at In VC++ 6.0 there is a batch file created during installation that
sets the environment.
In a typical installation that would be found at
"\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT" "\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT"

View File

@ -46,9 +46,6 @@
/* Define to 1 if you have the `z' library (-lz). */ /* Define to 1 if you have the `z' library (-lz). */
#undef HAVE_LIBZ #undef HAVE_LIBZ
/* Define to 1 if you have the <memory.h> header file. */
#undef HAVE_MEMORY_H
/* Define to 1 if you have the <netdb.h> header file. */ /* Define to 1 if you have the <netdb.h> header file. */
#undef HAVE_NETDB_H #undef HAVE_NETDB_H
@ -76,6 +73,9 @@
/* Define to 1 if you have the <stdint.h> header file. */ /* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H #undef HAVE_STDINT_H
/* Define to 1 if you have the <stdio.h> header file. */
#undef HAVE_STDIO_H
/* Define to 1 if you have the <stdlib.h> header file. */ /* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H #undef HAVE_STDLIB_H
@ -136,10 +136,15 @@
/* Define to the one symbol short name of this package. */ /* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME #undef PACKAGE_TARNAME
/* Define to the home page for this package. */
#undef PACKAGE_URL
/* Define to the version of this package. */ /* Define to the version of this package. */
#undef PACKAGE_VERSION #undef PACKAGE_VERSION
/* Define to 1 if you have the ANSI C header files. */ /* Define to 1 if all of the C90 standard headers exist (not just the ones
required in a freestanding environment). This macro is provided for
backward compatibility; new code need not use it. */
#undef STDC_HEADERS #undef STDC_HEADERS
/* Define to empty if `const' does not conform to ANSI C. */ /* Define to empty if `const' does not conform to ANSI C. */
@ -151,13 +156,13 @@
/* Define to `unsigned long' if <sys/types.h> does not define. */ /* Define to `unsigned long' if <sys/types.h> does not define. */
#undef in_addr_t #undef in_addr_t
/* Define to `long' if <sys/types.h> does not define. */ /* Define to `long int' if <sys/types.h> does not define. */
#undef off_t #undef off_t
/* Define to `int' if <sys/types.h> does not define. */ /* Define as a signed integer type capable of holding a process identifier. */
#undef pid_t #undef pid_t
/* Define to `unsigned' if <sys/types.h> does not define. */ /* Define to `unsigned int' if <sys/types.h> does not define. */
#undef size_t #undef size_t
/* Define to `int' if <sys/types.h> doesn't define. */ /* Define to `int' if <sys/types.h> doesn't define. */

8807
upstream/spamc/configure vendored

File diff suppressed because it is too large Load Diff

View File

@ -88,20 +88,34 @@ else
{ {
# These are the defaults for the Makefile. # These are the defaults for the Makefile.
my %env = ( my %env = (
CC => 'cl', CC => 'gcc',
WINCFLAGS => '/DWIN32 /W4', WINCFLAGS => '',
SSLCFLAGS => '/DSPAMC_SSL', SSLCFLAGS => '-DSPAMC_SSL',
SRCDIR => $srcdir, SRCDIR => $srcdir,
WINLIBS => 'ws2_32.lib', WINLIBS => '-lws2_32 -o spamc.exe',
SSLLIBS => 'ssleay32.lib libeay32.lib', SSLLIBS => '-lcrypto -lssl',
SPAMC_FILES => 'spamc.c getopt.c', SPAMC_FILES => 'spamc.c getopt.c',
LIBSPAMC_FILES => 'libspamc.c utils.c', LIBSPAMC_FILES => 'libspamc.c utils.c',
); );
my $cl_found = 0;
foreach my $path (File::Spec->path()) {
if( -x $path . '\cl.exe' ) {
$cl_found = 1;
}
}
if($cl_found) {
$env{'CC'} = 'cl';
$env{'WINCFLAGS'} = '/DWIN32 /W4';
$env{'SSLCFLAGS'} = '/DSPAMC_SSL';
$env{'WINLIBS'} = 'ws2_32.lib';
$env{'SSLLIBS'} = 'ssleay32.lib libeay32.lib';
}
# Enable SSL only if requested. # Enable SSL only if requested.
if ($args{'enable-ssl'} and $args{'enable-ssl'} ne 'yes') { if ($args{'enable-ssl'} and $args{'enable-ssl'} ne 'yes') {
delete $env{SSLCFLAGS}; delete $env{SSLCFLAGS};

View File

@ -124,9 +124,9 @@ Send log messages to stderr, instead of to the syslog.
=item B<-L> I<learn type>, B<--learntype>=I<type> =item B<-L> I<learn type>, B<--learntype>=I<type>
Send message to spamd for learning. The C<learn type> can be either spam, Send message to spamd for learning. The C<learn type> can be either spam,
ham or forget. The exitcode for spamc will be set to 5 if the message ham or forget. The exitcode for spamc will be set to 0 if the message
was learned, or 6 if it was already learned, under a condition that was learned or if it had already been learned. Non-zero exitcodes indicate an
a B<--no-safe-fallback> option is selected too. actual failure of some sort.
Note that the C<spamd> must run with the C<--allow-tell> option for Note that the C<spamd> must run with the C<--allow-tell> option for
this to work. this to work.
@ -373,7 +373,7 @@ C<Mail::SpamAssassin>
=head1 AUTHORS =head1 AUTHORS
The SpamAssassin(tm) Project <https://spamassassin.apache.org/> The SpamAssassin(tm) Project E<lt>https://spamassassin.apache.org/E<gt>
=head1 COPYRIGHT =head1 COPYRIGHT

View File

@ -621,7 +621,7 @@ The following methods must be overloaded:
Information about the client. Information about the client.
=item C<new( spamtest => $sa_object, foo => 'bar', ... )> =item C<new( spamtest =E<gt> $sa_object, foo =E<gt> 'bar', ... )>
Creates new object; C<shift && bless { @_ }>, basically. Creates new object; C<shift && bless { @_ }>, basically.
@ -632,7 +632,7 @@ chdir, set $ENV, etc. Do not call this directly.
=item C<read_body()> =item C<read_body()>
Read body from the client, run $self->spamtest->parse and store result Read body from the client, run $self-E<gt>spamtest-E<gt>parse and store result
as the C<parsed> key. as the C<parsed> key.
=item C<read_headers()> =item C<read_headers()>

View File

@ -377,7 +377,7 @@ NetSet
=head1 BUGS =head1 BUGS
See <http://bugzilla.spamassassin.org/>. See E<lt>http://bugzilla.spamassassin.org/E<gt>.
=head1 SEE ALSO =head1 SEE ALSO

View File

@ -39,7 +39,7 @@ DoS this way.
=head1 BUGS =head1 BUGS
See <http://bugzilla.spamassassin.org/> See E<lt>http://bugzilla.spamassassin.org/E<gt>
=head1 SEE ALSO =head1 SEE ALSO

View File

@ -40,7 +40,7 @@ Mail::SpamAssassin::Spamd::Apache2::AclRFC1413 - check spamd's client ident
=head1 DESCRIPTION =head1 DESCRIPTION
Queries remote ident server using mod_ident.so, saves result in Queries remote ident server using mod_ident.so, saves result in
C<$c->notes()>. C<$c-E<gt>notes()>.
Returns C<Apache2::Const::FORBIDDEN> on failure. Returns C<Apache2::Const::FORBIDDEN> on failure.
@ -125,7 +125,7 @@ Nothing.
=head1 BUGS =head1 BUGS
See <http://bugzilla.spamassassin.org/> See E<lt>http://bugzilla.spamassassin.org/E<gt>
=head1 SEE ALSO =head1 SEE ALSO

View File

@ -161,7 +161,7 @@ push @directives, { # inherited
=item C<SANew key "value"> =item C<SANew key "value">
Additional arguments to C<Mail::SpamAssassin->new()>. Refer to Additional arguments to C<Mail::SpamAssassin-E<gt>new()>. Refer to
C<Mail::SpamAssassin(3)>. C<Mail::SpamAssassin(3)>.
=cut =cut
@ -559,7 +559,7 @@ C<@INC>, you can use something like:
=head1 BUGS =head1 BUGS
See <http://bugzilla.spamassassin.org/>. See E<lt>http://bugzilla.spamassassin.org/E<gt>.
=head1 SEE ALSO =head1 SEE ALSO

View File

@ -41,7 +41,7 @@ disable something else for whatever reason... well, keep that in mind.
Error messages are not unified. Error messages are not unified.
See <http://bugzilla.spamassassin.org/> See E<lt>http://bugzilla.spamassassin.org/E<gt>
=head1 SEE ALSO =head1 SEE ALSO

View File

@ -57,6 +57,7 @@ our ($have_getaddrinfo_in_core, $have_getaddrinfo_legacy, $io_socket_module_name
# don't force requirement on IO::Socket::IP or IO::Socket::INET6 # don't force requirement on IO::Socket::IP or IO::Socket::INET6
BEGIN { BEGIN {
require Socket;
$have_getaddrinfo_in_core = eval { $have_getaddrinfo_in_core = eval {
# The Socket module (1.94) bundled with Perl 5.14.* provides # The Socket module (1.94) bundled with Perl 5.14.* provides
# new affordances for IPv6, including implementations of the # new affordances for IPv6, including implementations of the
@ -83,7 +84,6 @@ BEGIN {
&NI_NUMERICHOST; &NI_NUMERICSERV; &NI_NAMEREQD; 1; &NI_NUMERICHOST; &NI_NUMERICSERV; &NI_NAMEREQD; 1;
}; };
require Socket;
Socket->import(qw(:DEFAULT IPPROTO_TCP)); Socket->import(qw(:DEFAULT IPPROTO_TCP));
&SOCK_STREAM; &IPPROTO_TCP; &SOMAXCONN; # enable inlining &SOCK_STREAM; &IPPROTO_TCP; &SOMAXCONN; # enable inlining
@ -3294,6 +3294,9 @@ sub do_sighup_restart {
chdir($ORIG_CWD) chdir($ORIG_CWD)
or die "spamd: restart failed: chdir failed: ${ORIG_CWD}: $!\n"; or die "spamd: restart failed: chdir failed: ${ORIG_CWD}: $!\n";
# Close GeoDB, Geo::IP leaks fds on restart (Bug 8127)
delete $spamtest->{geodb};
# ensure we re-run spamd using the right perl interpreter, and # ensure we re-run spamd using the right perl interpreter, and
# with the right switches (taint mode and warnings) (bug 5255) # with the right switches (taint mode and warnings) (bug 5255)
# Also need -I options (bug 8030) because there is no way # Also need -I options (bug 8030) because there is no way
@ -3476,12 +3479,12 @@ Print a brief help message, then exit without further action.
Print version information, then exit without further action. Print version information, then exit without further action.
=item B<-i> [I<ipaddress>[:<port>]], B<--listen>[=I<ipaddress>[:<port>]] =item B<-i> [I<ipaddress>[:E<lt>portE<gt>]], B<--listen>[=I<ipaddress>[:E<lt>portE<gt>]]
Additional alias names for this option are --listen-ip and --ip-address. Additional alias names for this option are --listen-ip and --ip-address.
Tells spamd to listen on the specified IP address, defaults to a loopback Tells spamd to listen on the specified IP address, defaults to a loopback
interface, i.e. C<--listen localhost>). If no value is specified after the interface, i.e. C<--listen localhost>). If no value is specified after the
switch, or if an asterisk '*' stands in place of an <ipaddress>, spamd will switch, or if an asterisk '*' stands in place of an E<lt>ipaddressE<gt>, spamd will
listen on all interfaces - this is equivalent to address '0.0.0.0' for IPv4 listen on all interfaces - this is equivalent to address '0.0.0.0' for IPv4
and to '::' for IPv6. You can also use a valid hostname which will make spamd and to '::' for IPv6. You can also use a valid hostname which will make spamd
listen on all addresses that a name resolves to. The option may be specified listen on all addresses that a name resolves to. The option may be specified

View File

@ -130,7 +130,7 @@ CREATE TABLE userpref (
prefid int(11) NOT NULL auto_increment, prefid int(11) NOT NULL auto_increment,
PRIMARY KEY (prefid), PRIMARY KEY (prefid),
INDEX (username) INDEX (username)
) TYPE=MyISAM; ) ENGINE=InnoDB;
For PostgreSQL, use the following command: For PostgreSQL, use the following command:

View File

@ -113,7 +113,7 @@ CREATE TABLE awl (
totscore float NOT NULL default '0', totscore float NOT NULL default '0',
signedby varchar(255) NOT NULL default '', signedby varchar(255) NOT NULL default '',
PRIMARY KEY (username,email,signedby,ip) PRIMARY KEY (username,email,signedby,ip)
) TYPE=MyISAM; ) ENGINE=InnoDB;
For PostgreSQL, use the following: For PostgreSQL, use the following:

View File

@ -15,7 +15,7 @@ CREATE TRIGGER [UpdateLastHit]
AFTER UPDATE AFTER UPDATE
ON txrep ON txrep
FOR EACH ROW FOR EACH ROW
WHEN NEW.last_hit < OLD.last_hit WHEN NEW.last_hit <= OLD.last_hit
BEGIN BEGIN
UPDATE txrep SET last_hit=CURRENT_TIMESTAMP UPDATE txrep SET last_hit=CURRENT_TIMESTAMP
WHERE (username=OLD.username AND email=OLD.email AND signedby=OLD.signedby AND ip=OLD.ip); WHERE (username=OLD.username AND email=OLD.email AND signedby=OLD.signedby AND ip=OLD.ip);

View File

@ -5,4 +5,4 @@ CREATE TABLE userpref (
prefid int(11) NOT NULL auto_increment, prefid int(11) NOT NULL auto_increment,
PRIMARY KEY (prefid), PRIMARY KEY (prefid),
KEY username (username) KEY username (username)
) TYPE=MyISAM; ) ENGINE=InnoDB;

View File

@ -203,9 +203,8 @@ sub sa_t_init {
(-f "t/test_dir") && chdir("t"); # run from .. (-f "t/test_dir") && chdir("t"); # run from ..
-f "test_dir" or die "FATAL: not in test directory?\n"; -f "test_dir" or die "FATAL: not in test directory?\n";
unless (-d "log") { mkdir ("log", 0755);
mkdir ("log", 0755) or die ("Error creating log dir: $!"); -d "log" or die "FATAL: failed to create log dir\n";
}
chmod (0755, "log"); # set in case log already exists with wrong permissions chmod (0755, "log"); # set in case log already exists with wrong permissions
if (!$RUNNING_ON_WINDOWS) { if (!$RUNNING_ON_WINDOWS) {
@ -222,6 +221,7 @@ sub sa_t_init {
# individual work directory to make parallel tests possible # individual work directory to make parallel tests possible
$workdir = tempdir("$tname.XXXXXX", DIR => "log"); $workdir = tempdir("$tname.XXXXXX", DIR => "log");
die "FATAL: failed to create workdir: $!" unless -d $workdir; die "FATAL: failed to create workdir: $!" unless -d $workdir;
chmod (0755, $workdir); # sometimes tempdir() ignores umask
$keep_workdir = 0; $keep_workdir = 0;
# $siterules contains all stock *.pre files # $siterules contains all stock *.pre files
$siterules = "$workdir/siterules"; $siterules = "$workdir/siterules";
@ -748,7 +748,7 @@ sub start_spamd {
last if ($spamd_pid); last if ($spamd_pid);
} }
my $sleep = (int($wait++ / 4) + 1); my $sleep = (int(($wait++) / 4) + 1);
warn "spam_pid not found: Sleeping $sleep - Retry # $retries\n" if $retries && $retries < 20; warn "spam_pid not found: Sleeping $sleep - Retry # $retries\n" if $retries && $retries < 20;
sleep $sleep if $retries > 0; sleep $sleep if $retries > 0;

View File

@ -13,8 +13,8 @@ use Test::More;
plan skip_all => "Net tests disabled" unless conf_bool('run_net_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 skip_all => "Can't use Net::DNS Safely" unless can_use_net_dns_safely();
my $tests = 4; my $tests = 6;
$tests += 3 if (HAS_DKIM_VERIFIER); $tests += 4 if (HAS_DKIM_VERIFIER);
plan tests => $tests; plan tests => $tests;
@ -57,3 +57,14 @@ ok sarun ("-t -D < data/nice/001 2>&1", \&patterns_run_cb);
ok_all_patterns(); ok_all_patterns();
clear_pattern_counters(); clear_pattern_counters();
tstlocalrules(q{
askdns ASKDNS_TXT_SPF2 txttcp.spamassassin.org TXT /^v=spf1 include:dnsbltest.spamassassin.org -all$/
});
%patterns = (
q{ ASKDNS_TXT_SPF2 } => 'ASKDNS_TXT_SPF2',
'[txttcp.spamassassin.org TXT:v=spf1]' => 'ASKDNS_TXT_SPF2_LOG',
);
ok sarun ("-t -D < data/nice/001 2>&1", \&patterns_run_cb);
ok_all_patterns();
clear_pattern_counters();

View File

@ -25,9 +25,9 @@ tstprefs("
%patterns = ( %patterns = (
'parsing Authentication-Results: authrestest1int', 'hdr1', 'parsing Authentication-Results: authrestest1int', 'hdr1',
'parsing Authentication-Results: authrestest2int', 'hdr2', 'parsing Authentication-Results: authrestest2int', 'hdr2',
'parsing Authentication-Results: authrestest3int', 'hdr3', 'parsing authentication-Results: authrestest3int', 'hdr3',
'parsing Authentication-Results: authrestest4int', 'hdr4', 'parsing Authentication-Results: authrestest4int', 'hdr4',
'parsing Authentication-Results: authrestest5int', 'hdr5', 'parsing Authentication-RESULTS: authrestest5int', 'hdr5',
'parsing Authentication-Results: authrestest6int', 'hdr6', 'parsing Authentication-Results: authrestest6int', 'hdr6',
'authres: results: dkim=pass dmarc=none spf=pass', 'results', 'authres: results: dkim=pass dmarc=none spf=pass', 'results',
); );
@ -58,9 +58,9 @@ tstprefs("
%patterns = ( %patterns = (
'parsing Authentication-Results: authrestest1int', 'hdr1', 'parsing Authentication-Results: authrestest1int', 'hdr1',
'parsing Authentication-Results: authrestest2int', 'hdr2', 'parsing Authentication-Results: authrestest2int', 'hdr2',
'parsing Authentication-Results: authrestest3int', 'hdr3', 'parsing authentication-Results: authrestest3int', 'hdr3',
'parsing Authentication-Results: authrestest4int', 'hdr4', 'parsing Authentication-Results: authrestest4int', 'hdr4',
'parsing Authentication-Results: authrestest5int', 'hdr5', 'parsing Authentication-RESULTS: authrestest5int', 'hdr5',
'parsing Authentication-Results: authrestest6int', 'hdr6', 'parsing Authentication-Results: authrestest6int', 'hdr6',
'parsing Authentication-Results: authrestest7tru', 'hdr7', 'parsing Authentication-Results: authrestest7tru', 'hdr7',
'authres: results: dkim=pass dmarc=none spf=pass', 'results', 'authres: results: dkim=pass dmarc=none spf=pass', 'results',
@ -92,9 +92,9 @@ tstprefs("
%patterns = ( %patterns = (
'parsing Authentication-Results: authrestest1int', 'hdr1', 'parsing Authentication-Results: authrestest1int', 'hdr1',
'parsing Authentication-Results: authrestest2int', 'hdr2', 'parsing Authentication-Results: authrestest2int', 'hdr2',
'parsing Authentication-Results: authrestest3int', 'hdr3', 'parsing authentication-Results: authrestest3int', 'hdr3',
'parsing Authentication-Results: authrestest4int', 'hdr4', 'parsing Authentication-Results: authrestest4int', 'hdr4',
'parsing Authentication-Results: authrestest5int', 'hdr5', 'parsing Authentication-RESULTS: authrestest5int', 'hdr5',
'parsing Authentication-Results: authrestest6int', 'hdr6', 'parsing Authentication-Results: authrestest6int', 'hdr6',
'parsing Authentication-Results: authrestest7tru', 'hdr7', 'parsing Authentication-Results: authrestest7tru', 'hdr7',
'parsing Authentication-Results: authrestest8ext', 'hdr8', 'parsing Authentication-Results: authrestest8ext', 'hdr8',

View File

@ -5,12 +5,16 @@ use SATest; sa_t_init("basic_lint");
use Test::More; use Test::More;
tstpre ("
loadplugin Mail::SpamAssassin::Plugin::AWL
");
@test_locales = qw(C); @test_locales = qw(C);
if (!$RUNNING_ON_WINDOWS) { if (!$RUNNING_ON_WINDOWS) {
# Test with few random additional locales if available # Test with few random additional locales if available
my $locales = untaint_cmd("locale -a"); my $locales = untaint_cmd("locale -a");
while ($locales =~ /^((?:C|en_US|fr_FR|zh_CN)\.(?:utf|iso|gb).*)$/gmi) { while ($locales =~ /^((?:C|en_US|fr_FR|zh_CN)\.(?:utf|iso).*)$/gmi) {
push @test_locales, $1; push @test_locales, $1;
} }
} }

View File

@ -10,6 +10,10 @@ plan tests => 2;
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
tstpre ("
loadplugin Mail::SpamAssassin::Plugin::AWL
");
%patterns = ( %patterns = (
qr/^/, 'anything', qr/^/, 'anything',
); );

View File

@ -0,0 +1,53 @@
#!/usr/bin/perl -T
use lib '.'; use lib 't';
use SATest; sa_t_init("basic_lint_without_plugins");
use Test::More;
plan tests => 4;
# ---------------------------------------------------------------------------
%patterns = (
qr/^/, 'anything',
);
%anti_patterns = (
q{ . }, 'should be silent on success',
);
# override locale for this test!
$ENV{'LANGUAGE'} = $ENV{'LC_ALL'} = 'C';
# Comment out any loadplugin other than Check
foreach $tainted (<$workdir/*/*.pre>) {
$tainted =~ /(.*)/;
my $file = $1;
open(IN, $file) or die;
open(OUT, ">$file.tmp") or die;
while (<IN>) {
s/^loadplugin(?!.*::Check\b)/#loadplugin/;
print OUT $_ or die;
}
close OUT or die;
close IN or die;
rename("$file.tmp", "$file") or die;
}
# Just want to test sa-update rules
unlink("$localrules/01_test_rules.cf");
unlink("$localrules/99_test_default.cf");
my $scoresfile = "$localrules/50_scores.cf";
# when running from the built tarball or make disttest, we will not have a full
# rules dir -- therefore no 50_scores.cf,
# so we can use that to tell if this is the case
SKIP: {
skip( "Not on a repo checkout", 4 ) unless -f $scoresfile;
sarun ("--lint", \&patterns_run_cb);
ok_all_patterns();
sarun ("--lint --net", \&patterns_run_cb);
ok_all_patterns();
}

View File

@ -12,6 +12,10 @@ use Test::More tests => 3;
qr/^/, 'anything', qr/^/, 'anything',
); );
tstpre ("
loadplugin Mail::SpamAssassin::Plugin::AWL
");
# override locale for this test! # override locale for this test!
$ENV{'LANGUAGE'} = $ENV{'LC_ALL'} = 'C'; $ENV{'LANGUAGE'} = $ENV{'LC_ALL'} = 'C';

View File

@ -21,11 +21,7 @@ run_ipv6_dns_tests=n
run_dcc_tests=n run_dcc_tests=n
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Run SQL-based user pref tests during 'make test' REQUIRES DBD::SQLite 1.59_01 or later # Run SQL-based Auto-welcomelist tests during 'make test'
run_sql_pref_tests=n
# ---------------------------------------------------------------------------
# Run SQL-based Auto-whitelist tests during 'make test'
# NOTE: AWL test is always run with DBD::SQLite when available, only enable # NOTE: AWL test is always run with DBD::SQLite when available, only enable
# this when you want to additionally test for example MySQL or PostgresSQL # this when you want to additionally test for example MySQL or PostgresSQL
# (for which database needs to be created manually and configured below). # (for which database needs to be created manually and configured below).

View File

@ -4,7 +4,6 @@ dns_query_restriction deny *
dns_query_restriction allow spamassassin.org dns_query_restriction allow spamassassin.org
# Load selection of non-default plugins for all tests # Load selection of non-default plugins for all tests
loadplugin Mail::SpamAssassin::Plugin::AWL
loadplugin Mail::SpamAssassin::Plugin::RelayCountry loadplugin Mail::SpamAssassin::Plugin::RelayCountry
loadplugin Mail::SpamAssassin::Plugin::DCC loadplugin Mail::SpamAssassin::Plugin::DCC
loadplugin Mail::SpamAssassin::Plugin::TextCat loadplugin Mail::SpamAssassin::Plugin::TextCat

26
upstream/t/data/nice/spf4 Normal file
View File

@ -0,0 +1,26 @@
Return-Path: <newsalerts-noreply@txttcp.spamassassin.org>
Received: from txttcp.spamassassin.org (txttcp.spamassassin.org
[64.142.3.173]) by amgod.boxhost.net (Postfix) with SMTP id B9B2931016D for
<jm-google-news-alerts@jmason.org>; Tue, 10 Feb 2004 18:18:49 +0000 (GMT)
Received: by proxy.google.com with SMTP id so1951389 for
<jm-google-news-alerts@jmason.org>; Tue, 10 Feb 2004 10:14:01 -0800 (PST)
Received: by abbulk2 with SMTP id mr733125; Tue,
10 Feb 2004 10:14:01 -0800 (PST)
Message-ID: <1076436841.67074.8fa05ccdc458abe5.1446041b@persist.google.com>
Date: Tue, 10 Feb 2004 10:14:01 -0800 (PST)
From: newsalerts-noreply@txttcp.spamassassin.org
To: jm-google-news-alerts@jmason.org
Subject: Google News Alert - spamassassin
MIME-Version: 1.0
Content-Type: text/plain; charset="ISO-8859-1";
SWSOFT Unveils Plesk 7, Deployed by 1&1
Web Host Industry Review - USA
... The software also features a newly designed Windows XP-like user interface,
is equipped SpamAssassin, an open source anti-spam tool, and includes
"Application ...
<http://thewhir.com/marketwatch/sws021004.cfm>
See all stories on this topic:
<http://news.google.com/news?hl=en&lr=&ie=UTF-8&oe=utf8&client=google&num=30&newsc
lusterurl=http://thewhir.com/marketwatch/sws021004.cfm>

Some files were not shown because too many files have changed in this diff Show More