FreeBSD Bugzilla – Attachment 140242 Details for
Bug 186756
[UPDATE] mail/p5-Mail-SpamAssassin: update to 3.4.0
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
p5-Mail-SpamAssassin-3.4.0.patch
p5-Mail-SpamAssassin-3.4.0.patch (text/plain), 66.65 KB, created by
takefu
on 2014-02-14 08:10:00 UTC
(
hide
)
Description:
p5-Mail-SpamAssassin-3.4.0.patch
Filename:
MIME Type:
Creator:
takefu
Created:
2014-02-14 08:10:00 UTC
Size:
66.65 KB
patch
obsolete
>diff -ruN /usr/ports/mail/p5-Mail-SpamAssassin/Makefile ./Makefile >--- /usr/ports/mail/p5-Mail-SpamAssassin/Makefile 2014-01-22 05:17:40.000000000 +0900 >+++ ./Makefile 2014-02-14 14:55:06.000000000 +0900 >@@ -2,8 +2,8 @@ > # $FreeBSD: head/mail/p5-Mail-SpamAssassin/Makefile 340651 2014-01-21 20:17:40Z mat $ > > PORTNAME= Mail-SpamAssassin >-PORTVERSION= 3.3.2 >-PORTREVISION?= 8 # committer: please bump PORTREVISION on Slaves >+PORTVERSION= 3.4.0 >+PORTREVISION?= 0 # committer: please bump PORTREVISION on Slaves > CATEGORIES?= mail perl5 > MASTER_SITES= ${MASTER_SITE_APACHE:S/$/:apache/} ${MASTER_SITE_PERL_CPAN:S/$/:cpan/} > MASTER_SITE_SUBDIR= spamassassin/source/:apache Mail/:cpan >@@ -206,6 +206,8 @@ > @${INSTALL_DATA} ${WRKSRC}/spamc/libspamc.h ${STAGEDIR}${PREFIX}/include > > post-install:: >+ ${MKDIR} ${STAGEDIR}/var/lib/spamassassin ${STAGEDIR}${DBDIR}/spamassassin/ >+ > .if ${PORT_OPTIONS:MSPAMC} > @${STRIP_CMD} ${STAGEDIR}${PREFIX}/bin/spamc > .endif >@@ -216,5 +218,6 @@ > @${INSTALL_DATA} ${DOCSSQL:S|^|${WRKSRC}/sql/|} ${STAGEDIR}${DOCSDIR}/sql > @${INSTALL_DATA} ${DOCSLDAP:S|^|${WRKSRC}/ldap/|} ${STAGEDIR}${DOCSDIR}/ldap > .endif >+ @${SED} -e 's#PREFIX#${PREFIX}#' ${PKGMESSAGE} > > .include <bsd.port.post.mk> >diff -ruN /usr/ports/mail/p5-Mail-SpamAssassin/distinfo ./distinfo >--- /usr/ports/mail/p5-Mail-SpamAssassin/distinfo 2014-01-23 00:30:13.000000000 +0900 >+++ ./distinfo 2014-02-14 14:56:40.000000000 +0900 >@@ -1,2 +1,2 @@ >-SHA256 (Mail-SpamAssassin-3.3.2.tar.gz) = 5323038939a0ef9fc97d5264defce3ae1d95e98b3a94c4c3b583341c927f32df >-SIZE (Mail-SpamAssassin-3.3.2.tar.gz) = 1208182 >+SHA256 (Mail-SpamAssassin-3.4.0.tar.gz) = 244914c30976844878a7f129fd503eb40986c68a3800f416c3a68b14507c0a64 >+SIZE (Mail-SpamAssassin-3.4.0.tar.gz) = 1269753 >diff -ruN /usr/ports/mail/p5-Mail-SpamAssassin/files/patch-bug6624 ./files/patch-bug6624 >--- /usr/ports/mail/p5-Mail-SpamAssassin/files/patch-bug6624 2014-01-23 02:40:44.000000000 +0900 >+++ ./files/patch-bug6624 1970-01-01 09:00:00.000000000 +0900 >@@ -1,88 +0,0 @@ >---- lib/Mail/SpamAssassin/BayesStore/MySQL.pm (revision 1138970) >-+++ lib/Mail/SpamAssassin/BayesStore/MySQL.pm (working copy) >-@@ -840,14 +840,28 @@ >- return 0; >- } >- >-+ # With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if >-+ # the row is inserted as a new row and 2 if an existing row is updated. >-+ # >-+ # Due to a MySQL server bug a value of 3 can be seen. >-+ # See: http://bugs.mysql.com/bug.php?id=46675 >-+ # When executing the INSERT ... ON DUPLICATE KEY UPDATE statement >-+ # and checking the rows return count: >-+ # mysql_client_found_rows = 0: The second INSERT returns a row count >-+ # of 2 in all MySQL versions. >-+ # mysql_client_found_rows = 1: The second INSERT returns this row count: >-+ # Before MySQL 5.1.20: 2 >-+ # MySQL 5.1.20: undef on Mac OS X, 139775481 on Linux (garbage?) >-+ # MySQL 5.1.21 and up: 3 >-+ # >- my $num_rows = $rc; >- >- $sth->finish(); >- >-- if ($num_rows == 1 || $num_rows == 2) { >-+ if ($num_rows == 1 || $num_rows == 2 || $num_rows == 3) { >- my $token_count_update = ''; >- >-- $token_count_update = "token_count = token_count + 1," if ($num_rows == 1); >-+ $token_count_update = "token_count = token_count + 1," if $num_rows == 1; >- $sql = "UPDATE bayes_vars SET >- $token_count_update >- newest_token_age = GREATEST(newest_token_age, ?), >-@@ -872,7 +886,11 @@ >- } >- else { >- # $num_rows was not what we expected >-- dbg("bayes: _put_token: Updated an unexpected number of rows."); >-+ my $token_displ = $token; >-+ $token_displ =~ s/(.)/sprintf('%02x',ord($1))/egs; >-+ dbg("bayes: _put_token: Updated an unexpected number of rows: %s, ". >-+ "id: %s, token (hex): %s", >-+ $num_rows, $self->{_userid}, $token_displ); >- $self->{_dbh}->rollback(); >- return 0; >- } >-@@ -987,8 +1005,24 @@ >- else { >- my $num_rows = $rc; >- >-- $need_atime_update_p = 1 if ($num_rows == 1 || $num_rows == 2); >-- $new_tokens++ if ($num_rows == 1); >-+ # With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if >-+ # the row is inserted as a new row and 2 if an existing row is updated. >-+ # But see MySQL bug (as above): http://bugs.mysql.com/bug.php?id=46675 >-+ >-+ if ($num_rows == 1) { >-+ $new_tokens++; >-+ $need_atime_update_p = 1; >-+ } elsif ($num_rows == 2 || $num_rows == 3) { >-+ $need_atime_update_p = 1; >-+ } else { >-+ # $num_rows was not what we expected >-+ my $token_displ = $token; >-+ $token_displ =~ s/(.)/sprintf('%02x',ord($1))/egs; >-+ dbg("bayes: _put_tokens: Updated an unexpected number of rows: %s, ". >-+ "id: %s, token (hex): %s", >-+ $num_rows, $self->{_userid}, $token_displ); >-+ $error_p = 1; >-+ } >- } >- } >- >-@@ -1026,10 +1060,10 @@ >- } >- } >- else { >-- # $num_rows was not what we expected >-- dbg("bayes: _put_tokens: Updated an unexpected number of rows."); >-- $self->{_dbh}->rollback(); >-- return 0; >-+ info("bayes: _put_tokens: no atime updates needed? Num of tokens: %d", >-+ scalar keys %{$tokens}); >-+# $self->{_dbh}->rollback(); >-+# return 0; >- } >- } >- >diff -ruN /usr/ports/mail/p5-Mail-SpamAssassin/files/patch-bug6655 ./files/patch-bug6655 >--- /usr/ports/mail/p5-Mail-SpamAssassin/files/patch-bug6655 2014-01-23 02:40:44.000000000 +0900 >+++ ./files/patch-bug6655 1970-01-01 09:00:00.000000000 +0900 >@@ -1,50 +0,0 @@ >-$FreeBSD: head/mail/p5-Mail-SpamAssassin/files/patch-bug6655 340725 2014-01-22 17:40:44Z mat $ >- >-https://issues.apache.org/SpamAssassin/show_bug.cgi?id=6655 >- >---- lib/Mail/SpamAssassin/Util.pm 2011-06-06 19:59:17.000000000 -0400 >-+++ lib/Mail/SpamAssassin/Util.pm 2011-08-26 17:12:19.000000000 -0400 >-@@ -1025,6 +1024,8 @@ >- return; >- } >- >-+ opendir(my $dh, $tmpdir) || die "Could not open $tmpdir: $!"; >-+ closedir $dh; >- my ($reportfile, $tmpfile); >- my $umask = umask 077; >- >-@@ -1052,7 +1053,10 @@ >- >- # ensure the file handle is not semi-open in some way >- if ($tmpfile) { >-- close $tmpfile or info("error closing $reportfile: $!"); >-+ if (! close $tmpfile) { >-+ info("error closing $reportfile: $!"); >-+ $tmpfile=undef; >-+ } >- } >- } >- >---- sa-update.raw 2011-06-24 13:38:50.000000000 -0400 >-+++ sa-update.raw 2011-08-29 09:38:50.000000000 -0400 >-@@ -677,9 +677,9 @@ >- >- # Write the content out to a temp file for GPG/Archive::Tar interaction >- dbg("channel: populating temp content file"); >-- open(TMP, ">$content_file") || die "fatal: can't write to content temp file $content_file: $!\n"; >-+ open(TMP, ">$content_file") || die "fatal: couldn't create content temp file $content_file: $!\n"; >- binmode TMP; >-- print TMP $content; >-+ print TMP $content || die "fatal: can't write to content temp file $content_file: $!\n"; >- close(TMP); >- >- # to sign : gpg -bas file >-@@ -695,7 +695,7 @@ >- die "fatal: couldn't create temp file for GPG signature: $!\n"; >- } >- binmode $tfh; >-- print $tfh $GPG; >-+ print $tfh $GPG || die "fatal: can't write temp file for GPG signature: $!\n"; >- close($tfh); >- >- dbg("gpg: calling gpg"); >diff -ruN /usr/ports/mail/p5-Mail-SpamAssassin/files/patch-bug6698 ./files/patch-bug6698 >--- /usr/ports/mail/p5-Mail-SpamAssassin/files/patch-bug6698 2014-01-23 02:40:44.000000000 +0900 >+++ ./files/patch-bug6698 1970-01-01 09:00:00.000000000 +0900 >@@ -1,1471 +0,0 @@ >---- lib/Mail/SpamAssassin/Plugin/DCC.pm 2011-06-06 19:59:17.000000000 -0400 >-+++ lib/Mail/SpamAssassin/Plugin/DCC.pm 2011-11-26 07:22:36.000000000 -0500 >-@@ -15,6 +15,20 @@ >- # limitations under the License. >- # </@LICENSE> >- >-+# Changes since SpamAssassin 3.3.2: >-+# support for DCC learning. See dcc_learn_score. >-+# deal with orphan dccifd sockets >-+# use `cdcc -q` to not stall waiting to find a DCC server when deciding >-+# whether DCC checks are enabled >-+# use dccproc -Q or dccifd query if a pre-existing X-DCC header shows >-+# the message has already been reported >-+# dccproc now uses -w /var/dcc/whiteclnt so it acts more like dccifd >-+# warn about the use of ancient versions of dccproc and dccifd >-+# turn off dccifd greylisting >-+# query instead of reporting mail messages that contain X-DCC headers and >-+# and so has probably already been reported >-+# try harder to find dccproc and cdcc when not explicitly configured >-+ >- =head1 NAME >- >- Mail::SpamAssassin::Plugin::DCC - perform DCC check of messages >-@@ -30,30 +44,31 @@ >- >- The DCC or Distributed Checksum Clearinghouse is a system of servers >- collecting and counting checksums of millions of mail messages. >--TheSpamAssassin.pm counts can be used by SpamAssassin to detect and >--reject or filter spam. >-- >--Because simplistic checksums of spam can be easily defeated, the main >--DCC checksums are fuzzy and ignore aspects of messages. The fuzzy >--checksums are changed as spam evolves. >-+The counts can be used by SpamAssassin to detect and filter spam. >- >--Note that DCC is disabled by default in C<init.pre> because it is not >--open source. See the DCC license for more details. >-+See http://www.dcc-servers.net/dcc/ for more information about DCC. >- >--See http://www.rhyolite.com/anti-spam/dcc/ for more information about >--DCC. >-+Note that DCC is disabled by default in C<v310.pre> because its use requires >-+software that is not distributed with SpamAssassin and that has license >-+restrictions for certain commercial uses. >-+See the DCC license at http://www.dcc-servers.net/dcc/LICENSE for details. >-+ >-+Enable it by uncommenting the "loadplugin Mail::SpamAssassin::Plugin::DCC" >-+confdir/v310.pre or by adding this line to your local.pre. It might also >-+be necessary to install a DCC package, port, rpm, or equivalent from your >-+operating system distributor or a tarball from the primary DCC source >-+at http://www.dcc-servers.net/dcc/#download >-+See also http://www.dcc-servers.net/dcc/INSTALL.html >- >- =head1 TAGS >- >- The following tags are added to the set, available for use in reports, >- header fields, other plugins, etc.: >- >-- _DCCB_ DCC server ID in a response >-- _DCCR_ response from DCC - header field body in X-DCC-*-Metrics >-- _DCCREP_ response from DCC - DCC reputation in percents (0..100) >-- >--Tag _DCCREP_ provides a nonempty value only with commercial DCC systems. >--This is the percentage of spam vs. ham sent from the first untrusted relay. >-+ _DCCB_ DCC server ID in X-DCC-*-Metrics header field name >-+ _DCCR_ X-DCC-*-Metrics header field body >-+ _DCCREP_ DCC Reputation or percent bulk mail (0..100) from >-+ commercial DCC software >- >- =cut >- >-@@ -75,8 +90,6 @@ >- use vars qw(@ISA); >- @ISA = qw(Mail::SpamAssassin::Plugin); >- >--use vars qw($have_inet6); >-- >- sub new { >- my $class = shift; >- my $mailsaobject = shift; >-@@ -87,7 +100,7 @@ >- >- # are network tests enabled? >- if ($mailsaobject->{local_tests_only}) { >-- $self->{dcc_disabled} = 1; >-+ $self->{use_dcc} = 0; >- dbg("dcc: local tests only, disabling DCC"); >- } >- else { >-@@ -128,20 +141,23 @@ >- >- =item dcc_fuz2_max NUMBER >- >--This option sets how often a message's body/fuz1/fuz2 checksum must have been >--reported to the DCC server before SpamAssassin will consider the DCC check as >--matched. >-- >--As nearly all DCC clients are auto-reporting these checksums, you should set >--this to a relatively high value, e.g. C<999999> (this is DCC's MANY count). >-+Sets how often a message's body/fuz1/fuz2 checksum must have been reported >-+to the DCC server before SpamAssassin will consider the DCC check hit. >-+C<999999> is DCC's MANY count. >- >- The default is C<999999> for all these options. >- >- =item dcc_rep_percent NUMBER >- >--Only commercial DCC systems provide DCC reputation information. This is the >--percentage of spam vs. ham sent from the first untrusted relay. It will hit >--on new spam from spam sources. Default is C<90>. >-+Only the commercial DCC software provides DCC Reputations. A DCC Reputation >-+is the percentage of bulk mail received from the last untrusted relay in the >-+path taken by a mail message as measured by all commercial DCC installations. >-+See http://www.rhyolite.com/dcc/reputations.html >-+You C<must> whitelist your trusted relays or MX servers with MX or >-+MXDCC lines in /var/dcc/whiteclnt as described in the main DCC man page >-+to avoid seeing your own MX servers as sources of bulk mail. >-+See http://www.dcc-servers.net/dcc/dcc-tree/dcc.html#White-and-Blacklists >-+The default is C<90>. >- >- =cut >- >-@@ -189,13 +205,9 @@ >- =item dcc_home STRING >- >- This option tells SpamAssassin where to find the dcc homedir. >--If not given, it will try to get dcc to specify one, and if that fails it >--will try dcc's own default homedir of '/var/dcc'. >--If C<dcc_path> is not specified, it will default to looking in >--C<dcc_home/bin> for dcc client instead of relying on SpamAssassin to find it >--in the current PATH. If it isn't found there, it will look in the current >--PATH. If a C<dccifd> socket is found in C<dcc_home> or specified explicitly, >--it will use that interface instead of C<dccproc>. >-+If not specified, try to use the locally configured directory >-+from the C<cdcc homedir> command. >-+Try /var/dcc if that command fails. >- >- =cut >- >-@@ -205,7 +217,7 @@ >- type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, >- code => sub { >- my ($self, $key, $value, $line) = @_; >-- if (!defined $value || !length $value) { >-+ if (!defined $value || $value eq '') { >- return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; >- } >- $value = untaint_file_path($value); >-@@ -223,14 +235,16 @@ >- >- =item dcc_dccifd_path STRING >- >--This option tells SpamAssassin where to find the dccifd socket. If >--C<dcc_dccifd_path> is not specified, it will default to looking for a socket >--named C<dccifd> in a directory C<dcc_home>. The C<dcc_dccifd_path> can be >--a Unix socket name (absolute path), or an INET socket specification in a form >--C<[host]:port> or C<host:port>, where a host can be an IPv4 or IPv6 address >--or a host name, and port is a TCP port number. In case of an IPv6 address the >--brackets are required syntax. If a C<dccifd> socket is found, the plugin will >--use it instead of C<dccproc>. >-+This option tells SpamAssassin where to find the dccifd socket instead >-+of a local Unix socket named C<dccifd> in the C<dcc_home> directory. >-+If a socket is specified or found, use it instead of C<dccproc>. >-+ >-+If specifed, C<dcc_dccifd_path> is the absolute path of local Unix socket >-+or an INET socket specified as C<[Host]:Port> or C<Host:Port>. >-+Host can be an IPv4 or IPv6 address or a host name >-+Port is a TCP port number. The brackets are required for an IPv6 address. >-+ >-+The default is C<undef>. >- >- =cut >- >-@@ -240,45 +254,60 @@ >- type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, >- code => sub { >- my ($self, $key, $value, $line) = @_; >-- $value = '' if !defined $value; >-- $self->{dcc_dccifd_path_raw} = $value; # for logging purposes >-- undef $self->{dcc_dccifd_host}; >-- undef $self->{dcc_dccifd_port}; >-- undef $self->{dcc_dccifd_socket}; >-- local($1,$2,$3); >-- if ($value eq '') { >-+ >-+ if (!defined $value || $value eq '') { >- return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; >-- } elsif ($value =~ m{^ (?: \[ ([^\]]*) \] | ([^:]*) ) : ([^:]*) \z}sx) { >-- # "[host]:port" or "host:port", where a host can be an IPv4 or IPv6 >-- # address or a host name, and port is a TCP port number or service name >-- my $host = defined $1 ? $1 : $2; >-- my $port = $3; >-- $self->{dcc_dccifd_host} = untaint_var($host); >-- $self->{dcc_dccifd_port} = untaint_var($port); >-- dbg("config: dcc_dccifd_path set to [%s]:%s", $host,$port); >-- } else { # assume a unix socket >-+ } >-+ >-+ local($1,$2,$3); >-+ if ($value =~ m{^ (?: \[ ([^\]]*) \] | ([^:]*) ) : ([^:]*) \z}sx) { >-+ my $host = untaint_var(defined $1 ? $1 : $2); >-+ my $port = untaint_var($3); >-+ if (!$host) { >-+ info("config: missing or bad host name in dcc_dccifd_path '$value'"); >-+ return $Mail::SpamAssassin::Conf::INVALID_VALUE; >-+ } >-+ if (!$port || $port !~ /^\d+\z/ || $port < 1 || $port > 65535) { >-+ info("config: bad TCP port number in dcc_dccifd_path '$value'"); >-+ return $Mail::SpamAssassin::Conf::INVALID_VALUE; >-+ } >-+ >-+ $self->{dcc_dccifd_host} = $host; >-+ $self->{dcc_dccifd_port} = $port; >-+ if ($host !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/) { >-+ # remember to try IPv6 if we can with a host name or non-IPv4 address >-+ $self->{dcc_dccifd_IPv6} = eval { require IO::Socket::INET6 }; >-+ } >-+ dbg("config: dcc_dccifd_path set to [%s]:%s", $host, $port); >-+ >-+ } else { >-+ # assume a unix socket >- if ($value !~ m{^/}) { >-- info("config: dcc_dccifd_path should be an absolute socket path"); >-+ info("config: dcc_dccifd_path '$value' is not an absolute path"); >- # return $Mail::SpamAssassin::Conf::INVALID_VALUE; # abort or accept? >- } >- $value = untaint_file_path($value); >-- # test disabled, dccifd may not yet be running at spamd startup time >-- # if (!-S $value) { >-- # info("config: dcc_dccifd_path '$value' isn't a local socket"); >-- # return $Mail::SpamAssassin::Conf::INVALID_VALUE; >-- # } >-+ >- $self->{dcc_dccifd_socket} = $value; >- dbg("config: dcc_dccifd_path set to local socket %s", $value); >-+ dbg("dcc: dcc_dccifd_path set to local socket %s", $value); >- } >-+ >-+ $self->{dcc_dccifd_path_raw} = $value; >- } >- }); >- >- =item dcc_path STRING >- >--This option tells SpamAssassin specifically where to find the C<dccproc> >--client instead of relying on SpamAssassin to find it in the current PATH. >--Note that if I<taint mode> is enabled in the Perl interpreter, you should >--use this, as the current PATH will have been cleared. >-+Where to find the C<dccproc> client program instead of relying on SpamAssassin >-+to find it in the current PATH or C<dcc_home/bin>. This must often be set, >-+because the current PATH is cleared by I<taint mode> in the Perl interpreter, >-+ >-+If a C<dccifd> socket is found in C<dcc_home> or specified explicitly >-+with C<dcc_dccifd_path>, use the C<dccifd(8)> interface instead of C<dccproc>. >-+ >-+The default is C<undef>. >-+ >- >- =cut >- >-@@ -289,12 +318,12 @@ >- type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, >- code => sub { >- my ($self, $key, $value, $line) = @_; >-- if (!defined $value || !length $value) { >-+ if (!defined $value || $value eq '') { >- return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; >- } >- $value = untaint_file_path($value); >- if (!-x $value) { >-- info("config: dcc_path '$value' isn't an executable"); >-+ info("config: dcc_path '$value' is not executable"); >- return $Mail::SpamAssassin::Conf::INVALID_VALUE; >- } >- >-@@ -304,7 +333,7 @@ >- >- =item dcc_options options >- >--Specify additional options to the dccproc(8) command. Please note that only >-+Specify additional options to the dccproc(8) command. Only >- characters in the range [0-9A-Za-z ,._/-] are allowed for security reasons. >- >- The default is C<undef>. >-@@ -319,6 +348,7 @@ >- code => sub { >- my ($self, $key, $value, $line) = @_; >- if ($value !~ m{^([0-9A-Za-z ,._/-]+)$}) { >-+ info("config: dcc_options '$value' contains impermissible characters"); >- return $Mail::SpamAssassin::Conf::INVALID_VALUE; >- } >- $self->{dcc_options} = $1; >-@@ -327,8 +357,9 @@ >- >- =item dccifd_options options >- >--Specify additional options to send to the dccifd(8) daemon. Please note that only >--characters in the range [0-9A-Za-z ,._/-] are allowed for security reasons. >-+Specify additional options to send to the dccifd daemon with >-+the ASCII protocol described on the dccifd(8) man page. >-+Only characters in the range [0-9A-Za-z ,._/-] are allowed for security reasons. >- >- The default is C<undef>. >- >-@@ -342,265 +373,306 @@ >- code => sub { >- my ($self, $key, $value, $line) = @_; >- if ($value !~ m{^([0-9A-Za-z ,._/-]+)$}) { >-+ info("config: dccifd_options '$value' contains impermissible characters"); >- return $Mail::SpamAssassin::Conf::INVALID_VALUE; >- } >- $self->{dccifd_options} = $1; >- } >- }); >- >-+=item dcc_learn_score n (default: undef) >-+ >-+Report messages with total scores this much larger than the >-+SpamAssassin spam threshold to DCC as spam. >-+ >-+=cut >-+ >-+ push (@cmds, { >-+ setting => 'dcc_learn_score', >-+ is_admin => 1, >-+ default => undef, >-+ type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, >-+ }); >-+ >- $conf->{parser}->register_commands(\@cmds); >- } >- >-+ >-+ >-+ >-+sub ck_dir { >-+ my ($self, $dir, $tgt, $src) = @_; >-+ >-+ $dir = untaint_file_path($dir); >-+ if (!stat($dir)) { >-+ my $dir_errno = 0+$!; >-+ if ($dir_errno == ENOENT) { >-+ dbg("dcc: $tgt $dir from $src does not exist"); >-+ } else { >-+ dbg("dcc: $tgt $dir from $src is not accessible: $!"); >-+ } >-+ return; >-+ } >-+ if (!-d _) { >-+ dbg("dcc: $tgt $dir from $src is not a directory"); >-+ return; >-+ } >-+ >-+ $self->{main}->{conf}->{$tgt} = $dir; >-+ dbg("dcc: use '$tgt $dir' from $src"); >-+} >-+ >- sub find_dcc_home { >- my ($self) = @_; >-+ my $dcc_libexec; >-+ >-+ # just once >-+ return if defined $self->{dcc_version}; >-+ $self->{dcc_version} = '?'; >- >- my $conf = $self->{main}->{conf}; >-- return if !$conf->{use_dcc}; >- >-- my $dcchome = $conf->{dcc_home} || ''; >- >-- # If we're not given the DCC homedir, try getting DCC to tell us it. >-- # If that fails, try the DCC default homedir of '/var/dcc'. >-- if ($dcchome eq '') { >-+ # Get the DCC software version for talking to dccifd and formating the >-+ # dccifd options and the built-in DCC homedir. Use -q to prevent delays. >-+ my $cdcc_home; >-+ my $cdcc = $self->dcc_pgm_path('cdcc'); >-+ my $cmd = '-qV homedir libexecdir'; >-+ if ($cdcc && open(CDCC, "$cdcc $cmd 2>&1 |")) { >-+ my $cdcc_output = do { local $/ = undef; <CDCC> }; >-+ close CDCC; >- >-- my $cdcc = Mail::SpamAssassin::Util::find_executable_in_env_path('cdcc'); >-+ $cdcc_output =~ s/\n/ /g; # everything in 1 line for debugging >-+ dbg("dcc: `%s %s` reports '%s'", $cdcc, $cmd, $cdcc_output); >-+ $self->{dcc_version} = ($cdcc_output =~ /^(\d+\.\d+\.\d+)/) ? $1 : ''; >-+ $cdcc_home = ($cdcc_output =~ /\s+homedir=(\S+)/) ? $1 : ''; >-+ if ($cdcc_output =~ /\s+libexecdir=(\S+)/) { >-+ $self->ck_dir($1, 'dcc_libexec', 'cdcc'); >-+ } >-+ } >- >-- my $cdcc_home = ''; >-- if ($cdcc && -x $cdcc && open(CDCC, "$cdcc homedir 2>&1|")) { >-- dbg("dcc: dcc_home not set, querying cdcc utility"); >-- $cdcc_home = <CDCC> || ''; >-- close CDCC; >-+ # without a home, try the homedir from cdcc >-+ if (!$conf->{dcc_home} && $cdcc_home) { >-+ $self->ck_dir($cdcc_home, 'dcc_home', 'cdcc'); >-+ } >-+ # finally fall back to /var/dcc >-+ if (!$conf->{dcc_home}) { >-+ $self->ck_dir($conf->{dcc_home} = '/var/dcc', 'dcc_home', 'default') >-+ } >- >-- chomp $cdcc_home; >-- $cdcc_home =~ s/\s+homedir=//; >-- dbg("dcc: cdcc reports homedir as '%s'", $cdcc_home); >-- } >-- >-- # try first with whatever the cdcc utility reported >-- my $cdcc_home_errno = 0; >-- if ($cdcc_home eq '') { >-- $cdcc_home_errno = ENOENT; >-- } elsif (!stat($cdcc_home)) { >-- $cdcc_home_errno = 0+$!; >-- } >-- if ($cdcc_home_errno == ENOENT) { >-- # no such file >-- } elsif ($cdcc_home_errno != 0) { >-- dbg("dcc: cdcc reported homedir $cdcc_home is not accessible: $!"); >-- } elsif (!-d _) { >-- dbg("dcc: cdcc reported homedir $cdcc_home is not a directory"); >-- } else { # ok >-- dbg("dcc: cdcc reported homedir $cdcc_home exists, using it"); >-- $dcchome = untaint_var($cdcc_home); >-- } >-- >-- # try falling back to /var/dcc >-- if ($dcchome eq '') { >-- my $var_dcc_errno = stat('/var/dcc') ? 0 : 0+$!; >-- if ($var_dcc_errno == ENOENT) { >-- # no such file >-- } elsif ($var_dcc_errno != 0) { >-- dbg("dcc: dcc_home not set and dcc default homedir /var/dcc ". >-- "is not accessible: $!"); >-- } elsif (!-d _) { >-- dbg("dcc: dcc_home not set and dcc default homedir /var/dcc ". >-- "is not a directory"); >-- } else { # ok >-- dbg("dcc: dcc_home not set but dcc default homedir /var/dcc exists, ". >-- "using it"); >-- $dcchome = '/var/dcc'; >-+ # fall back to $conf->{dcc_home}/libexec or /var/dcc/libexec for dccsight >-+ if (!$conf->{dcc_libexec}) { >-+ $self->ck_dir($conf->{dcc_home} . '/libexec', 'dcc_libexec', 'dcc_home'); >- } >-+ if (!$conf->{dcc_libexec}) { >-+ $self->ck_dir('/var/dcc/libexec', 'dcc_libexec', 'dcc_home'); >- } >- >-- if ($dcchome eq '') { >-- dbg("dcc: unable to get homedir from cdcc ". >-- "and the dcc default homedir was not found"); >-- } >-- >-- # Remember found homedir path >-- dbg("dcc: using '%s' as DCC homedir", $dcchome); >-- $conf->{dcc_home} = $dcchome; >-+ # format options for dccifd >-+ my $opts = ($conf->{dccifd_options} || '') . "\n"; >-+ if ($self->{dcc_version} =~ /\d+\.(\d+)\.(\d+)$/ && >-+ ($1 < 3 || ($1 == 3 && $2 < 123))) { >-+ if ($1 < 3 || ($1 == 3 && $2 < 50)) { >-+ info("dcc: DCC version $self->{dcc_version} is years old, ". >-+ "obsolete, and likely to cause problems. ". >-+ "See http://www.dcc-servers.net/dcc/old-versions.html"); >-+ } >-+ $self->{dccifd_lookup_options} = "header " . $opts; >-+ $self->{dccifd_report_options} = "header spam " . $opts; >-+ } else { >-+ # dccifd after version 1.2.123 understands "cksums" and "no-grey" >-+ $self->{dccifd_lookup_options} = "cksums grey-off " . $opts; >-+ $self->{dccifd_report_options} = "header spam grey-off " . $opts; >- } >- } >- >--sub is_dccifd_available { >-- my ($self) = @_; >-- >-+sub dcc_pgm_path { >-+ my ($self, $pgm) = @_; >-+ my $pgmpath; >- my $conf = $self->{main}->{conf}; >-- $self->{dccifd_available} = 0; >- >-- if (!$conf->{use_dcc}) { >-- dbg("dcc: dccifd is not available: use_dcc is false"); >-- } elsif (defined $conf->{dcc_dccifd_host}) { >-- dbg("dcc: dccifd inet socket chosen: [%s]:%s", >-- $conf->{dcc_dccifd_host}, $conf->{dcc_dccifd_port}); >-- $self->{dccifd_available} = 1; >-- } else { >-- my $sockpath = $conf->{dcc_dccifd_socket}; >-- my $dcchome = $conf->{dcc_home}; >-- if (defined $sockpath) { >-- dbg("dcc: dccifd local socket chosen: %s", $sockpath); >-- } elsif (defined $conf->{dcc_dccifd_path_raw}) { >-- # avoid falling back to defaults if explicitly provided but wrong >-- } elsif (defined $dcchome && $dcchome ne '' && -S "$dcchome/dccifd") { >-- $sockpath = "$dcchome/dccifd"; >-- $conf->{dcc_dccifd_socket} = $sockpath; >-- dbg("dcc: dccifd default local socket chosen: %s", $sockpath); >-+ $pgmpath = $conf->{dcc_path}; >-+ if (defined $pgmpath && $pgmpath ne '') { >-+ # accept explicit setting for dccproc >-+ return $pgmpath if $pgm eq 'dccproc'; >-+ # try adapting it for cdcc and everything else >-+ if ($pgmpath =~ s{[^/]+\z}{$pgm}s) { >-+ $pgmpath = untaint_file_path($pgmpath); >-+ if (-x $pgmpath) { >-+ dbg("dcc: dcc_pgm_path, found %s in dcc_path: %s", $pgm,$pgmpath); >-+ return $pgmpath; >- } >-- if (defined $sockpath && -S $sockpath && -w _ && -r _) { >-- $self->{dccifd_available} = 1; >-- } elsif (!defined $conf->{dcc_dccifd_path_raw}) { >-- dbg("dcc: dccifd is not available: no r/w dccifd socket found"); >-- } else { >-- dbg("dcc: dccifd is not available: no r/w dccifd socket found: %s", >-- $conf->{dcc_dccifd_path_raw}); >- } >- } >- >-- return $self->{dccifd_available}; >-+ $pgmpath = Mail::SpamAssassin::Util::find_executable_in_env_path($pgm); >-+ if (defined $pgmpath) { >-+ dbg("dcc: dcc_pgm_path, found %s in env.path: %s", $pgm,$pgmpath); >-+ return $pgmpath; >-+ } >-+ >-+ # try dcc_home/bin, dcc_libexec, and some desperate last attempts >-+ foreach my $dir ($conf->{dcc_home}.'/bin', $conf->{dcc_libexec}, >-+ '/usr/local/bin', '/usr/local/dcc', '/var/dcc') { >-+ $pgmpath = $dir . '/' . $pgm; >-+ if (-x $pgmpath) { >-+ dbg("dcc: dcc_pgm_path, found %s in %s: %s", $pgm,$dir,$pgmpath); >-+ return $pgmpath; >-+ } >-+ } >-+ >-+ return; >- } >- >--sub is_dccproc_available { >-+sub is_dccifd_available { >- my ($self) = @_; >- my $conf = $self->{main}->{conf}; >- >-- $self->{dccproc_available} = 0; >-+ # dccifd remains available until it breaks >-+ return $self->{dccifd_available} if $self->{dccifd_available}; >- >-- if (!$conf->{use_dcc}) { >-- dbg("dcc: dccproc is not available: use_dcc is false"); >-- return 0; >-+ # deal with configured INET socket >-+ if (defined $conf->{dcc_dccifd_host}) { >-+ dbg("dcc: dccifd is available via INET socket [%s]:%s", >-+ $conf->{dcc_dccifd_host}, $conf->{dcc_dccifd_port}); >-+ return ($self->{dccifd_available} = 1); >- } >-- my $dcchome = $conf->{dcc_home} || ''; >-- my $dccproc = $conf->{dcc_path} || ''; >- >-- if ($dccproc eq '' && ($dcchome ne '' && -x "$dcchome/bin/dccproc")) { >-- $dccproc = "$dcchome/bin/dccproc"; >-+ # the first time here, compute a default local socket based on DCC home >-+ # from self->find_dcc_home() called elsewhere >-+ my $sockpath = $conf->{dcc_dccifd_socket}; >-+ if (!$sockpath) { >-+ if ($conf->{dcc_dccifd_path_raw}) { >-+ $sockpath = $conf->{dcc_dccifd_path_raw}; >-+ } else { >-+ $sockpath = "$conf->{dcc_home}/dccifd"; >- } >-- if ($dccproc eq '') { >-- $dccproc = Mail::SpamAssassin::Util::find_executable_in_env_path('dccproc'); >-+ $conf->{dcc_dccifd_socket} = $sockpath; >- } >- >-- unless (defined $dccproc && $dccproc ne '' && -x $dccproc) { >-- dbg("dcc: dccproc is not available: no dccproc executable found"); >-- return 0; >-- } >-+ # check the socket every time because it can appear and disappear >-+ return ($self->{dccifd_available} = 1) if (-S $sockpath && -w _ && -r _); >- >-- # remember any found dccproc >-+ dbg("dcc: dccifd is not available; no r/w socket at %s", $sockpath); >-+ return ($self->{dccifd_available} = 0); >-+} >-+ >-+sub is_dccproc_available { >-+ my ($self) = @_; >-+ my $conf = $self->{main}->{conf}; >-+ >-+ # dccproc remains (un)available so check only once >-+ return $self->{dccproc_available} if defined $self->{dccproc_available}; >-+ >-+ my $dccproc = $conf->{dcc_path}; >-+ if (!defined $dccproc || $dccproc eq '') { >-+ $dccproc = $self->dcc_pgm_path('dccproc'); >- $conf->{dcc_path} = $dccproc; >-+ if (!$dccproc || ! -x $dccproc) { >-+ dbg("dcc: dccproc is not available: no dccproc executable found"); >-+ return ($self->{dccproc_available} = 0); >-+ } >-+ } >- >-- dbg("dcc: dccproc is available: %s", $conf->{dcc_path}); >-- $self->{dccproc_available} = 1; >-- return 1; >-+ dbg("dcc: %s is available", $conf->{dcc_path}); >-+ return ($self->{dccproc_available} = 1); >- } >- >- sub dccifd_connect { >-- my($self) = @_; >-+ my($self, $tag) = @_; >- my $conf = $self->{main}->{conf}; >- my $sockpath = $conf->{dcc_dccifd_socket}; >-- my $host = $conf->{dcc_dccifd_host}; >-- my $port = $conf->{dcc_dccifd_port}; >- my $sock; >-+ >- if (defined $sockpath) { >-- dbg("dcc: connecting to a local socket %s", $sockpath); >-- $sock = IO::Socket::UNIX->new( >-- Type => SOCK_STREAM, Peer => $sockpath); >-- $sock or die "dcc: failed to connect to a socket $sockpath: $!\n"; >-- } elsif (defined $host) { >-- my $specified_path = $conf->{dcc_dccifd_path_raw}; >-- if ($host eq '') { >-- die "dcc: empty host specification: $specified_path\n"; >-- } >-- if (!defined $port || $port !~ /^\d+\z/ || $port < 1 || $port > 65535) { >-- die "dcc: bad TCP port number: $specified_path\n"; >-- } >-- my $is_inet4 = $host =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/; >-- if ($is_inet4) { # inet4 socket (IPv4 address) >-- dbg("dcc: connecting to inet4 socket [%s]:%s", $host,$port); >-- $sock = IO::Socket::INET->new( >-- Proto => 'tcp', PeerAddr => $host, PeerPort => $port); >-- } else { >-- if (!defined $have_inet6) { >-- $have_inet6 = eval { require IO::Socket::INET6 }; >-- $have_inet6 = 0 if !defined $have_inet6; >-+ $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM, Peer => $sockpath); >-+ if ($sock) { >-+ dbg("$tag connected to local socket %s", $sockpath); >-+ return $sock; >- } >-- if (!$have_inet6) { # fallback to an inet4 socket (IPv4) >-- dbg("dcc: connecting(2) to inet4 socket [%s]:%s", $host,$port); >-- $sock = IO::Socket::INET->new( >-- Proto => 'tcp', PeerAddr => $host, PeerPort => $port); >-- } else { # inet6 socket (IPv6) or a host name >-- dbg("dcc: connecting to inet6 socket [%s]:%s", $host,$port); >-+ $self->{dccifd_available} = 0; >-+ info("$tag failed to connect to local socket $sockpath"); >-+ return $sock >-+ } >-+ >-+ # must be TCP/IP >-+ my $host = $conf->{dcc_dccifd_host}; >-+ my $port = $conf->{dcc_dccifd_port}; >-+ >-+ if ($conf->{dcc_dccifd_IPv6}) { >-+ # try IPv6 if we can with a host name or non-IPv4 address >-+ dbg("$tag connecting to inet6 socket [%s]:%s", $host,$port); >- $sock = IO::Socket::INET6->new( >- Proto => 'tcp', PeerAddr => $host, PeerPort => $port); >-+ # fall back to IPv4 if that failed >- } >-+ if (!$sock) { >-+ dbg("$tag connecting to inet4 socket [%s]:%s", $host, $port); >-+ $sock = IO::Socket::INET->new( >-+ Proto => 'tcp', PeerAddr => $host, PeerPort => $port); >- } >-- $sock or die "dcc: failed to connect to [$host]:$port : $!\n"; >-- } else { >-- die "dcc: dccifd socket not provided: $conf->{dcc_dccifd_path_raw}\n"; >-- } >-+ >-+ info("failed to connect to [$host]:$port : $!") if !$sock; >- return $sock; >- } >- >-+# check for dccifd every time in case enough uses of dccproc starts dccifd >- sub get_dcc_interface { >- my ($self) = @_; >-+ my $conf = $self->{main}->{conf}; >- >-- if ($self->is_dccifd_available()) { >-- $self->{dcc_interface} = "dccifd"; >-- $self->{dcc_disabled} = 0; >-- } >-- elsif ($self->is_dccproc_available()) { >-- $self->{dcc_interface} = "dccproc"; >-- $self->{dcc_disabled} = 0; >-+ if (!$conf->{use_dcc}) { >-+ $self->{dcc_disabled} = 1; >-+ return; >- } >-- else { >-- dbg("dcc: dccifd and dccproc are not available, disabling DCC"); >-- $self->{dcc_interface} = "none"; >-+ >-+ $self->find_dcc_home(); >-+ if (!$self->is_dccifd_available() && !$self->is_dccproc_available()) { >-+ dbg("dcc: dccifd and dccproc are not available"); >- $self->{dcc_disabled} = 1; >- } >-+ >-+ $self->{dcc_disabled} = 0; >- } >- >- sub dcc_query { >-- my ($self, $permsgstatus, $full) = @_; >-+ my ($self, $permsgstatus, $fulltext) = @_; >- >- $permsgstatus->{dcc_checked} = 1; >- >-+ if (!$self->{main}->{conf}->{use_dcc}) { >-+ dbg("dcc: DCC is not available: use_dcc is 0"); >-+ return; >-+ } >-+ >- # initialize valid tags >- $permsgstatus->{tag_data}->{DCCB} = ""; >- $permsgstatus->{tag_data}->{DCCR} = ""; >- $permsgstatus->{tag_data}->{DCCREP} = ""; >- >-- # short-circuit if there's already a X-DCC header with value of >-- # "bulk" from an upstream DCC check >-- if ($permsgstatus->get('ALL') =~ >-- /^(X-DCC-([^:]{1,80})?-?Metrics:.*bulk.*)$/m) { >-- $permsgstatus->{dcc_response} = $1; >-+ if ($$fulltext eq '') { >-+ dbg("dcc: empty message; skipping dcc check"); >- return; >- } >- >-- my $timer = $self->{main}->time_method("check_dcc"); >-+ if ($permsgstatus->get('ALL') =~ /^(X-DCC-.*-Metrics:.*)$/m) { >-+ $permsgstatus->{dcc_raw_x_dcc} = $1; >-+ # short-circuit if there is already a X-DCC header with value of >-+ # "bulk" from an upstream DCC check >-+ # require "bulk" because then at least one body checksum will be "many" >-+ # and so we know the X-DCC header is not forged by spammers >-+ return if $permsgstatus->{dcc_raw_x_dcc} =~ / bulk /; >-+ } >- >-- $self->find_dcc_home(); >-+ my $timer = $self->{main}->time_method("check_dcc"); >- >- $self->get_dcc_interface(); >-- my $result; >-- if ($self->{dcc_disabled}) { >-- $result = 0; >-- } elsif ($$full eq '') { >-- dbg("dcc: empty message, skipping dcc check"); >-- $result = 0; >-- } elsif ($self->{dccifd_available}) { >-- my $client = $permsgstatus->{relays_external}->[0]->{ip}; >-- my $clientname = $permsgstatus->{relays_external}->[0]->{rdns}; >-- my $helo = $permsgstatus->{relays_external}->[0]->{helo} || ""; >-- if ($client) { >-- $client = $client . "\r" . $clientname if $clientname; >-- } else { >-- $client = "0.0.0.0"; >-- } >-- $self->dccifd_lookup($permsgstatus, $full, $client, $clientname, $helo); >-- } else { >-- my $client = $permsgstatus->{relays_external}->[0]->{ip}; >-- $self->dccproc_lookup($permsgstatus, $full, $client); >-- } >-+ return if $self->{dcc_disabled}; >-+ >-+ my $envelope = $permsgstatus->{relays_external}->[0]; >-+ ($permsgstatus->{dcc_raw_x_dcc}, >-+ $permsgstatus->{dcc_cksums}) = $self->ask_dcc("dcc:", $permsgstatus, >-+ $fulltext, $envelope); >- } >- >- sub check_dcc { >-@@ -609,28 +681,27 @@ >- >- $self->dcc_query($permsgstatus, $full) if !$permsgstatus->{dcc_checked}; >- >-- my $response = $permsgstatus->{dcc_response}; >-- return 0 if !defined $response || $response eq ''; >-+ my $x_dcc = $permsgstatus->{dcc_raw_x_dcc}; >-+ return 0 if !defined $x_dcc || $x_dcc eq ''; >- >-- local($1,$2); >-- if ($response =~ /^X-DCC-(.*)-Metrics: (.*)$/) { >-- $permsgstatus->{tag_data}->{DCCB} = $1; >-- $permsgstatus->{tag_data}->{DCCR} = $2; >-+ if ($x_dcc =~ /^X-DCC-(.*)-Metrics: (.*)$/) { >-+ $permsgstatus->set_tag('DCCB', $1); >-+ $permsgstatus->set_tag('DCCR', $2); >- } >-- $response =~ s/many/999999/ig; >-- $response =~ s/ok\d?/0/ig; >-+ $x_dcc =~ s/many/999999/ig; >-+ $x_dcc =~ s/ok\d?/0/ig; >- >- my %count = (body => 0, fuz1 => 0, fuz2 => 0, rep => 0); >-- if ($response =~ /\bBody=(\d+)/) { >-+ if ($x_dcc =~ /\bBody=(\d+)/) { >- $count{body} = $1+0; >- } >-- if ($response =~ /\bFuz1=(\d+)/) { >-+ if ($x_dcc =~ /\bFuz1=(\d+)/) { >- $count{fuz1} = $1+0; >- } >-- if ($response =~ /\bFuz2=(\d+)/) { >-+ if ($x_dcc =~ /\bFuz2=(\d+)/) { >- $count{fuz2} = $1+0; >- } >-- if ($response =~ /\brep=(\d+)/) { >-+ if ($x_dcc =~ /\brep=(\d+)/) { >- $count{rep} = $1+0; >- } >- if ($count{body} >= $conf->{dcc_body_max} || >-@@ -651,185 +722,185 @@ >- } >- >- sub check_dcc_reputation_range { >-- my ($self, $permsgstatus, $full, $min, $max) = @_; >-- $self->dcc_query($permsgstatus, $full) if !$permsgstatus->{dcc_checked}; >-+ my ($self, $permsgstatus, $fulltext, $min, $max) = @_; >-+ >-+ # this is called several times per message, so parse the X-DCC header once >-+ my $dcc_rep = $permsgstatus->{dcc_rep}; >-+ if (!defined $dcc_rep) { >-+ $self->dcc_query($permsgstatus, $fulltext) if !$permsgstatus->{dcc_checked}; >-+ my $x_dcc = $permsgstatus->{dcc_raw_x_dcc}; >-+ if (defined $x_dcc && $x_dcc =~ /\brep=(\d+)/) { >-+ $dcc_rep = $1+0; >-+ $permsgstatus->set_tag('DCCREP', $dcc_rep); >-+ } else { >-+ $dcc_rep = -1; >-+ } >-+ $permsgstatus->{dcc_rep} = $dcc_rep; >-+ } >- >-- my $response = $permsgstatus->{dcc_response}; >-- return 0 if !defined $response || $response eq ''; >-+ # no X-DCC header or no reputation in the X-DCC header, perhaps for lack >-+ # of data in the DCC Reputation server >-+ return 0 if $dcc_rep < 0; >- >-+ # cover the entire range of reputations if not told otherwise >- $min = 0 if !defined $min; >-- $max = 999 if !defined $max; >-+ $max = 100 if !defined $max; >- >-- local $1; >-- my $dcc_rep; >-- $dcc_rep = $1+0 if defined $response && $response =~ /\brep=(\d+)/; >-- if (defined $dcc_rep) { >-- $dcc_rep = int($dcc_rep); # just in case, rule ranges are integer percents >- my $result = $dcc_rep >= $min && $dcc_rep <= $max ? 1 : 0; >- dbg("dcc: dcc_rep %s, min %s, max %s => result=%s", >- $dcc_rep, $min, $max, $result?'YES':'no'); >-- $permsgstatus->{tag_data}->{DCCREP} = $dcc_rep; >-- return $dcc_rep >= $min && $dcc_rep <= $max ? 1 : 0; >-+ return $result; >-+} >-+ >-+# get the X-DCC header line and save the checksums from dccifd or dccproc >-+sub parse_dcc_response { >-+ my ($self, $resp) = @_; >-+ my ($raw_x_dcc, $cksums); >-+ >-+ # The first line is the header we want. It uses SMTP folded whitespace >-+ # if it is long. The folded whitespace is always a single \t. >-+ chomp($raw_x_dcc = shift @$resp); >-+ my $v; >-+ while (($v = shift @$resp) && $v =~ s/^\t(.+)\s*\n/ $1/) { >-+ $raw_x_dcc .= $v; >-+ } >-+ >-+ # skip the "reported:" line between the X-DCC header and any checksums >-+ # remove ':' to avoid a bug in versions 1.3.115 - 1.3.122 in dccsight >-+ # with the length of "Message-ID:" >-+ $cksums = ''; >-+ while (($v = shift @$resp) && $v =~ s/^([^:]*):/$1/) { >-+ $cksums .= $v; >- } >-- return 0; >-+ >-+ return ($raw_x_dcc, $cksums); >- } >- >--sub dccifd_lookup { >-- my ($self, $permsgstatus, $fulltext, $client, $clientname, $helo) = @_; >-+sub ask_dcc { >-+ my ($self, $tag, $permsgstatus, $fulltext, $envelope) = @_; >- my $conf = $self->{main}->{conf}; >-- my $response; >-- my $left; >-- my $right; >-- my $timeout = $conf->{dcc_timeout}; >-- my $opts = $conf->{dccifd_options}; >-- my @opts = !defined $opts ? () : split(' ',$opts); >-+ my ($pgm, $err, $sock, $pid, @resp); >-+ my ($client, $clientname, $helo, $opts); >- >- $permsgstatus->enter_helper_run_mode(); >- >-+ my $timeout = $conf->{dcc_timeout}; >- my $timer = Mail::SpamAssassin::Timeout->new( >- { secs => $timeout, deadline => $permsgstatus->{master_deadline} }); >-- my $err = $timer->run_and_catch(sub { >- >-+ $err = $timer->run_and_catch(sub { >- local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; >- >-- my $sock = $self->dccifd_connect(); >-- $sock or die "dcc: failed to connect to a dccifd socket"; >-- >-- # send the options and other parameters to the daemon >-- $sock->print("header " . join(" ",@opts) . "\n") >-- or die "dcc: failed write"; # options >-- $sock->print($client . "\n") or die "dcc: failed write"; # client >-- $sock->print($helo . "\n") or die "dcc: failed write"; # HELO value >-- $sock->print("\n") or die "dcc: failed write"; # sender >-- $sock->print("unknown\r\n") or die "dcc: failed write"; # recipients >-- $sock->print("\n") or die "dcc: failed write"; # recipients >-- >-- $sock->print($$fulltext) or die "dcc: failed write"; >-- >-- $sock->shutdown(1) or die "dcc: failed socket shutdown: $!"; >-- >-- $sock->getline() or die "dcc: failed read status"; >-- $sock->getline() or die "dcc: failed read multistatus"; >-+ # prefer dccifd to dccproc >-+ if ($self->{dccifd_available}) { >-+ $pgm = 'dccifd'; >- >-- my @null = $sock->getlines(); >-- if (!@null) { >-- # no facility prefix on this >-- die "dcc: failed to read header\n"; >-- } >-+ $sock = $self->dccifd_connect($tag); >-+ if (!$sock) { >-+ $self->{dccifd_available} = 0; >-+ die("dccproc not available") if (!$self->is_dccproc_available()); >- >-- # the first line will be the header we want to look at >-- chomp($response = shift @null); >-- # but newer versions of DCC fold the header if it's too long... >-- while (my $v = shift @null) { >-- last unless ($v =~ s/^\s+/ /); # if this line wasn't folded, stop >-- chomp $v; >-- $response .= $v; >-+ # fall back on dccproc if the socket is an orphan from >-+ # a killed dccifd daemon or some other obvious (no timeout) problem >-+ dbg("$tag fall back on dccproc"); >- } >-- >-- dbg("dcc: dccifd got response: %s", $response); >-- >-- }); >-- >-- $permsgstatus->leave_helper_run_mode(); >-- >-- if ($timer->timed_out()) { >-- dbg("dcc: dccifd check timed out after $timeout secs."); >-- return; >- } >- >-- if ($err) { >-- chomp $err; >-- warn("dcc: dccifd -> check skipped: $err\n"); >-- return; >-- } >-+ if ($self->{dccifd_available}) { >- >-- if (!defined $response || $response !~ /^X-DCC/) { >-- dbg("dcc: dccifd check failed - no X-DCC returned: %s", $response); >-- return; >-+ # send the options and other parameters to the daemon >-+ $client = $envelope->{ip}; >-+ $clientname = $envelope->{rdns}; >-+ if (!defined $client) { >-+ $client = ''; >-+ } else { >-+ $client .= ("\r" . $clientname) if defined $clientname; >- } >-+ $helo = $envelope->{helo} || ''; >-+ if ($tag ne "dcc:") { >-+ $opts = $self->{dccifd_report_options} >-+ } else { >-+ $opts = $self->{dccifd_lookup_options}; >-+ # only query if there is an X-DCC header >-+ $opts =~ s/grey-off/& query/ if defined $permsgstatus->{dcc_raw_x_dcc}; >-+ } >-+ $sock->print($opts) or die "failed write options\n"; >-+ $sock->print($client . "\n") or die "failed write SMTP client\n"; >-+ $sock->print($helo . "\n") or die "failed write HELO value\n"; >-+ $sock->print("\n") or die "failed write sender\n"; >-+ $sock->print("unknown\n\n") or die "failed write 1 recipient\n"; >-+ $sock->print($$fulltext) or die "failed write mail message\n"; >-+ $sock->shutdown(1) or die "failed socket shutdown: $!"; >- >-- $response =~ s/[ \t]\z//; # strip trailing whitespace >-- $permsgstatus->{dcc_response} = $response; >--} >-+ $sock->getline() or die "failed read status\n"; >-+ $sock->getline() or die "failed read multistatus\n"; >- >--sub dccproc_lookup { >-- my ($self, $permsgstatus, $fulltext, $client) = @_; >-- my $conf = $self->{main}->{conf}; >-- my $response; >-- my %count = (body => 0, fuz1 => 0, fuz2 => 0, rep => 0); >-- my $timeout = $conf->{dcc_timeout}; >-+ @resp = $sock->getlines(); >-+ die "failed to read dccifd response\n" if !@resp; >- >-- $permsgstatus->enter_helper_run_mode(); >-- >-- # use a temp file here -- open2() is unreliable, buffering-wise, under spamd >-+ } else { >-+ $pgm = 'dccproc'; >-+ # use a temp file -- open2() is unreliable, buffering-wise, under spamd >-+ # first ensure that we do not hit a stray file from some other filter. >-+ $permsgstatus->delete_fulltext_tmpfile(); >- my $tmpf = $permsgstatus->create_fulltext_tmpfile($fulltext); >-- my $pid; >-- >-- my $timer = Mail::SpamAssassin::Timeout->new( >-- { secs => $timeout, deadline => $permsgstatus->{master_deadline} }); >-- my $err = $timer->run_and_catch(sub { >-- >-- local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; >- >-- # note: not really tainted, this came from system configuration file >-- my $path = untaint_file_path($conf->{dcc_path}); >-- >-- my $opts = $conf->{dcc_options}; >-+ my $path = $conf->{dcc_path}; >-+ $opts = $conf->{dcc_options}; >- my @opts = !defined $opts ? () : split(' ',$opts); >- untaint_var(\@opts); >-+ unshift(@opts, '-w', 'whiteclnt'); >-+ $client = $envelope->{ip}; >-+ if ($client) { >-+ unshift(@opts, '-a', untaint_var($client)); >-+ } else { >-+ # get external relay IP address from Received: header if not available >-+ unshift(@opts, '-R'); >-+ } >-+ if ($tag eq "dcc:") { >-+ # query instead of report if there is an X-DCC header from upstream >-+ unshift(@opts, '-Q') if defined $permsgstatus->{dcc_raw_x_dcc}; >-+ } else { >-+ # learn or report spam >-+ unshift(@opts, '-t', 'many'); >-+ } >- >-- unshift(@opts, "-a", >-- untaint_var($client)) if defined $client && $client ne ''; >-- >-- dbg("dcc: opening pipe: %s", >-- join(' ', $path, "-H", "-x", "0", @opts, "< $tmpf")); >-+ dbg("$tag opening pipe to %s", >-+ join(' ', $path, "-C", "-x", "0", @opts, "<$tmpf")); >- >- $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*DCC, >-- $tmpf, 1, $path, "-H", "-x", "0", @opts); >-+ $tmpf, 1, $path, "-C", "-x", "0", @opts); >- $pid or die "$!\n"; >- >- # read+split avoids a Perl I/O bug (Bug 5985) >- my($inbuf,$nread,$resp); $resp = ''; >- while ( $nread=read(DCC,$inbuf,8192) ) { $resp .= $inbuf } >- defined $nread or die "error reading from pipe: $!"; >-- my @null = split(/^/m, $resp, -1); undef $resp; >-+ @resp = split(/^/m, $resp, -1); undef $resp; >- >- my $errno = 0; close DCC or $errno = $!; >- proc_status_ok($?,$errno) >-- or info("dcc: [%s] finished: %s", $pid, exit_status_str($?,$errno)); >-- >-- if (!@null) { >-- # no facility prefix on this >-- die "failed to read header\n"; >-- } >-+ or info("$tag [%s] finished: %s", $pid, exit_status_str($?,$errno)); >- >-- # the first line will be the header we want to look at >-- chomp($response = shift @null); >-- # but newer versions of DCC fold the header if it's too long... >-- while (my $v = shift @null) { >-- last unless ($v =~ s/^\s+/ /); # if this line wasn't folded, stop >-- chomp $v; >-- $response .= $v; >-+ die "failed to read X-DCC header from dccproc\n" if !@resp; >- } >-- >-- unless (defined($response)) { >-- # no facility prefix on this >-- die "no response\n"; # yes, this is possible >-- } >-- >-- dbg("dcc: got response: %s", $response); >-- >- }); >- >-+ if ($pgm eq 'dccproc') { >- if (defined(fileno(*DCC))) { # still open >- if ($pid) { >-- if (kill('TERM',$pid)) { dbg("dcc: killed stale helper [$pid]") } >-- else { dbg("dcc: killing helper application [$pid] failed: $!") } >-+ if (kill('TERM',$pid)) { >-+ dbg("$tag killed stale dccproc process [$pid]") >-+ } else { >-+ dbg("$tag killing dccproc process [$pid] failed: $!") >-+ } >- } >- my $errno = 0; close(DCC) or $errno = $!; >-- proc_status_ok($?,$errno) >-- or info("dcc: [%s] terminated: %s", $pid, exit_status_str($?,$errno)); >-+ proc_status_ok($?,$errno) or info("$tag [%s] dccproc terminated: %s", >-+ $pid, exit_status_str($?,$errno)); >-+ } >- } >-+ >- $permsgstatus->leave_helper_run_mode(); >- >- if ($timer->timed_out()) { >-@@ -833,204 +904,182 @@ >- $permsgstatus->leave_helper_run_mode(); >- >- if ($timer->timed_out()) { >-- dbg("dcc: check timed out after $timeout seconds"); >-- return; >-+ dbg("$tag $pgm timed out after $timeout seconds"); >-+ return (undef, undef); >- } >- >- if ($err) { >- chomp $err; >-- if ($err eq "__brokenpipe__ignore__") { >-- dbg("dcc: check failed: broken pipe"); >-- } elsif ($err eq "no response") { >-- dbg("dcc: check failed: no response"); >-- } else { >-- warn("dcc: check failed: $err\n"); >-- } >-- return; >-+ info("$tag $pgm failed: $err\n"); >-+ return (undef, undef); >- } >- >-- if (!defined($response) || $response !~ /^X-DCC/) { >-- $response ||= ''; >-- dbg("dcc: check failed: no X-DCC returned (did you create a map file?): %s", $response); >-- return; >-+ my ($raw_x_dcc, $cksums) = $self->parse_dcc_response(\@resp); >-+ if (!defined $raw_x_dcc || $raw_x_dcc !~ /^X-DCC/) { >-+ info("$tag instead of X-DCC header, $pgm returned '%s'", $raw_x_dcc); >-+ return (undef, undef); >- } >-- >-- $permsgstatus->{dcc_response} = $response; >-+ dbg("$tag %s responded with '%s'", $pgm, $raw_x_dcc); >-+ return ($raw_x_dcc, $cksums); >- } >- >--# only supports dccproc right now >--sub plugin_report { >-+# tell DCC server that the message is spam according to SpamAssassin >-+sub check_post_learn { >- my ($self, $options) = @_; >- >-- return if $options->{report}->{options}->{dont_report_to_dcc}; >-- $self->get_dcc_interface(); >-- return if $self->{dcc_disabled}; >-- >-- # get the metadata from the message so we can pass the external relay information >-- $options->{msg}->extract_message_metadata($options->{report}->{main}); >-- my $client = $options->{msg}->{metadata}->{relays_external}->[0]->{ip}; >-- if ($self->{dccifd_available}) { >-- my $clientname = $options->{msg}->{metadata}->{relays_external}->[0]->{rdns}; >-- my $helo = $options->{msg}->{metadata}->{relays_external}->[0]->{helo} || ""; >-- if ($client) { >-- if ($clientname) { >-- $client = $client . "\r" . $clientname; >-- } >-- } else { >-- $client = "0.0.0.0"; >-- } >-- if ($self->dccifd_report($options, $options->{text}, $client, $helo)) { >-- $options->{report}->{report_available} = 1; >-- info("reporter: spam reported to DCC"); >-- $options->{report}->{report_return} = 1; >-+ # learn only if allowed >-+ return if $self->{learn_disabled}; >-+ my $conf = $self->{main}->{conf}; >-+ if (!$conf->{use_dcc}) { >-+ $self->{learn_disabled} = 1; >-+ return; >- } >-- else { >-- info("reporter: could not report spam to DCC via dccifd"); >-+ my $learn_score = $conf->{dcc_learn_score}; >-+ if (!defined $learn_score || $learn_score eq '') { >-+ dbg("dcc: DCC learning not enabled by dcc_learn_score"); >-+ $self->{learn_disabled} = 1; >-+ return; >- } >-- } else { >-- # use temporary file: open2() is unreliable due to buffering under spamd >-- my $tmpf = $options->{report}->create_fulltext_tmpfile($options->{text}); >- >-- if ($self->dcc_report($options, $tmpf, $client)) { >-- $options->{report}->{report_available} = 1; >-- info("reporter: spam reported to DCC"); >-- $options->{report}->{report_return} = 1; >-+ # and if SpamAssassin concluded that the message is spam >-+ # worse than our threshold >-+ my $permsgstatus = $options->{permsgstatus}; >-+ if ($permsgstatus->is_spam()) { >-+ my $score = $permsgstatus->get_score(); >-+ my $required_score = $permsgstatus->get_required_score(); >-+ if ($score < $required_score + $learn_score) { >-+ dbg("dcc: score=%d required_score=%d dcc_learn_score=%d", >-+ $score, $required_score, $learn_score); >-+ return; >- } >-- else { >-- info("reporter: could not report spam to DCC via dccproc"); >- } >-- $options->{report}->delete_fulltext_tmpfile(); >-+ >-+ # and if we checked the message >-+ return if (!defined $permsgstatus->{dcc_raw_x_dcc}); >-+ >-+ # and if the DCC server thinks it was not spam >-+ if ($permsgstatus->{dcc_raw_x_dcc} !~ /\b(Body|Fuz1|Fuz2)=\d/) { >-+ dbg("dcc: already known as spam; no need to learn"); >-+ return; >- } >-+ >-+ # dccsight is faster than dccifd or dccproc if we have checksums, >-+ # which we do not have with dccifd before 1.3.123 >-+ my $old_cksums = $permsgstatus->{dcc_cksums}; >-+ return if ($old_cksums && $self->dccsight_learn($permsgstatus, $old_cksums)); >-+ >-+ # Fall back on dccifd or dccproc without saved checksums or dccsight. >-+ # get_dcc_interface() was called when the message was checked >-+ >-+ # is getting the full text this way kosher? Is get_pristine() public? >-+ my $fulltext = $permsgstatus->{msg}->get_pristine(); >-+ my $envelope = $permsgstatus->{relays_external}->[0]; >-+ my ($raw_x_dcc, $cksums) = $self->ask_dcc("dcc: learn:", $permsgstatus, >-+ \$fulltext, $envelope); >-+ dbg("dcc: learned as spam") if defined $raw_x_dcc; >- } >- >--sub dccifd_report { >-- my ($self, $options, $fulltext, $client, $helo) = @_; >-- my $conf = $self->{main}->{conf}; >-- my $timeout = $conf->{dcc_timeout}; >-- # instead of header use whatever the report option is >-- my $opts = $conf->{dccifd_options}; >-- my @opts = !defined $opts ? () : split(' ',$opts); >-+sub dccsight_learn { >-+ my ($self, $permsgstatus, $old_cksums) = @_; >-+ my ($raw_x_dcc, $new_cksums); >-+ >-+ return 0 if !$old_cksums; >-+ >-+ my $dccsight = $self->dcc_pgm_path('dccsight'); >-+ if (!$dccsight) { >-+ info("dcc: cannot find dccsight") if $dccsight eq ''; >-+ return 0; >-+ } >- >-- $options->{report}->enter_helper_run_mode(); >-- my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout }); >-+ $permsgstatus->enter_helper_run_mode(); >- >-- my $err = $timer->run_and_catch(sub { >-+ # use a temp file here -- open2() is unreliable, buffering-wise, under spamd >-+ # ensure that we do not hit a stray file from some other filter. >-+ $permsgstatus->delete_fulltext_tmpfile(); >-+ my $tmpf = $permsgstatus->create_fulltext_tmpfile(\$old_cksums); >-+ my $pid; >- >-+ my $timeout = $self->{main}->{conf}->{dcc_timeout}; >-+ my $timer = Mail::SpamAssassin::Timeout->new( >-+ { secs => $timeout, deadline => $permsgstatus->{master_deadline} }); >-+ my $err = $timer->run_and_catch(sub { >- local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; >- >-- my $sock = $self->dccifd_connect(); >-- $sock or die "report: failed to connect to a dccifd socket"; >-+ dbg("dcc: opening pipe to %s", >-+ join(' ', $dccsight, "-t", "many", "<$tmpf")); >- >-- # send the options and other parameters to the daemon >-- $sock->print("spam " . join(" ",@opts) . "\n") >-- or die "report: dccifd failed write"; # options >-- $sock->print($client . "\n") >-- or die "report: dccifd failed write"; # client >-- $sock->print($helo . "\n") >-- or die "report: dccifd failed write"; # HELO value >-- $sock->print("\n") >-- or die "report: dccifd failed write"; # sender >-- $sock->print("unknown\r\n") >-- or die "report: dccifd failed write"; # recipients >-- $sock->print("\n") >-- or die "report: dccifd failed write"; # recipients >-+ $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*DCC, >-+ $tmpf, 1, $dccsight, "-t", "many"); >-+ $pid or die "$!\n"; >- >-- $sock->print($$fulltext) or die "report: dccifd failed write"; >-+ # read+split avoids a Perl I/O bug (Bug 5985) >-+ my($inbuf,$nread,$resp); $resp = ''; >-+ while ( $nread=read(DCC,$inbuf,8192) ) { $resp .= $inbuf } >-+ defined $nread or die "error reading from pipe: $!"; >-+ my @resp = split(/^/m, $resp, -1); undef $resp; >- >-- $sock->shutdown(1) or die "report: dccifd failed socket shutdown: $!"; >-+ my $errno = 0; close DCC or $errno = $!; >-+ proc_status_ok($?,$errno) >-+ or info("dcc: [%s] finished: %s", $pid, exit_status_str($?,$errno)); >- >-- $sock->getline() or die "report: dccifd failed read status"; >-- $sock->getline() or die "report: dccifd failed read multistatus"; >-+ die "dcc: failed to read learning response\n" if !@resp; >- >-- my @ignored = $sock->getlines(); >-+ ($raw_x_dcc, $new_cksums) = $self->parse_dcc_response(\@resp); >- }); >- >-- $options->{report}->leave_helper_run_mode(); >-+ if (defined(fileno(*DCC))) { # still open >-+ if ($pid) { >-+ if (kill('TERM',$pid)) { >-+ dbg("dcc: killed stale dccsight process [$pid]") >-+ } else { >-+ dbg("dcc: killing stale dccsight process [$pid] failed: $!") } >-+ } >-+ my $errno = 0; close(DCC) or $errno = $!; >-+ proc_status_ok($?,$errno) or info("dcc: dccsight [%s] terminated: %s", >-+ $pid, exit_status_str($?,$errno)); >-+ } >-+ $permsgstatus->delete_fulltext_tmpfile(); >-+ $permsgstatus->leave_helper_run_mode(); >- >- if ($timer->timed_out()) { >-- dbg("reporter: DCC report via dccifd timed out after $timeout secs."); >-+ dbg("dcc: dccsight timed out after $timeout seconds"); >- return 0; >- } >- >- if ($err) { >- chomp $err; >-- if ($err eq "__brokenpipe__ignore__") { >-- dbg("reporter: DCC report via dccifd failed: broken pipe"); >-- } else { >-- warn("reporter: DCC report via dccifd failed: $err\n"); >-- } >-+ info("dcc: dccsight failed: $err\n"); >- return 0; >- } >- >-+ if ($raw_x_dcc) { >-+ dbg("dcc: learned response: %s", $raw_x_dcc); >- return 1; >--} >-- >--sub dcc_report { >-- my ($self, $options, $tmpf, $client) = @_; >-- my $conf = $self->{main}->{conf}; >-- my $timeout = $options->{report}->{conf}->{dcc_timeout}; >-- >-- # note: not really tainted, this came from system configuration file >-- my $path = untaint_file_path($options->{report}->{conf}->{dcc_path}); >-- my $opts = $conf->{dcc_options}; >-- my @opts = !defined $opts ? () : split(' ',$opts); >-- untaint_var(\@opts); >-- >-- # get the metadata from the message so we can pass the external relay info >-- >-- unshift(@opts, "-a", >-- untaint_var($client)) if defined $client && $client ne ''; >-- >-- my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout }); >-- >-- $options->{report}->enter_helper_run_mode(); >-- my $err = $timer->run_and_catch(sub { >-- >-- local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; >-- >-- dbg("report: opening pipe: %s", >-- join(' ', $path, "-H", "-t", "many", "-x", "0", @opts, "< $tmpf")); >-- >-- my $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*DCC, >-- $tmpf, 1, $path, "-H", "-t", "many", "-x", "0", @opts); >-- $pid or die "$!\n"; >-+ } >- >-- my($inbuf,$nread,$nread_all); $nread_all = 0; >-- # response is ignored, just check its existence >-- while ( $nread=read(DCC,$inbuf,8192) ) { $nread_all += $nread } >-- defined $nread or die "error reading from pipe: $!"; >-+ return 0; >-+} >- >-- dbg("dcc: empty response") if $nread_all < 1; >-+sub plugin_report { >-+ my ($self, $options) = @_; >- >-- my $errno = 0; close DCC 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; >-- proc_status_ok($?,$errno) >-- or die "dcc: reporter error: ".exit_status_str($?,$errno)."\n"; >-- }); >-- $options->{report}->leave_helper_run_mode(); >-+ return if $options->{report}->{options}->{dont_report_to_dcc}; >-+ $self->get_dcc_interface(); >-+ return if $self->{dcc_disabled}; >- >-- if ($timer->timed_out()) { >-- dbg("reporter: DCC report via dccproc timed out after $timeout seconds"); >-- return 0; >-- } >-+ # get the metadata from the message so we can report the external relay >-+ $options->{msg}->extract_message_metadata($options->{report}->{main}); >-+ my $envelope = $options->{msg}->{metadata}->{relays_external}->[0]; >-+ my ($raw_x_dcc, $cksums) = $self->ask_dcc("reporter:", $options->{report}, >-+ $options->{text}, $envelope); >- >-- if ($err) { >-- chomp $err; >-- if ($err eq "__brokenpipe__ignore__") { >-- dbg("reporter: DCC report via dccproc failed: broken pipe"); >-+ if (defined $raw_x_dcc) { >-+ $options->{report}->{report_available} = 1; >-+ info("reporter: spam reported to DCC"); >-+ $options->{report}->{report_return} = 1; >- } else { >-- warn("reporter: DCC report via dccproc failed: $err\n"); >-+ info("reporter: could not report spam to DCC"); >- } >-- return 0; >-- } >-- >-- return 1; >- } >- >- 1; >-- >--=back >-- >--=cut >diff -ruN /usr/ports/mail/p5-Mail-SpamAssassin/files/patch-bug6745 ./files/patch-bug6745 >--- /usr/ports/mail/p5-Mail-SpamAssassin/files/patch-bug6745 2013-09-03 04:00:24.000000000 +0900 >+++ ./files/patch-bug6745 1970-01-01 09:00:00.000000000 +0900 >@@ -1,106 +0,0 @@ >---- lib/Mail/SpamAssassin/Logger/Syslog.pm 2012/05/14 16:28:23 1338277 >-+++ lib/Mail/SpamAssassin/Logger/Syslog.pm 2012/05/14 16:31:09 1338278 >-@@ -167,17 +167,21 @@ >- } >- $msg = $timestamp . ' ' . $msg if $timestamp ne ''; >- >-- # important: do not call syslog() from the SIGCHLD handler >-- # child_handler(). otherwise we can get into a loop if syslog() >-- # forks a process -- as it does in syslog-ng apparently! (bug 3625) >-- $Mail::SpamAssassin::Logger::LOG_SA{INHIBIT_LOGGING_IN_SIGCHLD_HANDLER} = 1; >-+# no longer needed since a patch to bug 6745: >-+# # important: do not call syslog() from the SIGCHLD handler >-+# # child_handler(). otherwise we can get into a loop if syslog() >-+# # forks a process -- as it does in syslog-ng apparently! (bug 3625) >-+# $Mail::SpamAssassin::Logger::LOG_SA{INHIBIT_LOGGING_IN_SIGCHLD_HANDLER} = 1; >-+ >- my $eval_stat; >- eval { >- syslog($level, "%s", $msg); 1; >- } or do { >- $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; >- }; >-- $Mail::SpamAssassin::Logger::LOG_SA{INHIBIT_LOGGING_IN_SIGCHLD_HANDLER} = 0; >-+ >-+# no longer needed since a patch to bug 6745: >-+# $Mail::SpamAssassin::Logger::LOG_SA{INHIBIT_LOGGING_IN_SIGCHLD_HANDLER} = 0; >- >- if (defined $eval_stat) { >- if ($self->check_syslog_sigpipe($msg)) { >---- spamd/spamd.raw 2012/05/14 16:28:23 1338277 >-+++ spamd/spamd.raw 2012/05/14 16:31:09 1338278 >-@@ -589,6 +589,7 @@ >- my $timeout_child; # processing timeout (headers->finish), 0=no timeout >- my $clients_per_child; # number of clients each child should process >- my %children; # current children >-+my @children_exited; >- >- if ( defined $opt{'max-children'} ) { >- $childlimit = $opt{'max-children'}; >-@@ -1033,6 +1034,8 @@ >- # child_handler() if !$scaling || am_running_on_windows(); >- child_handler(); # it doesn't hurt to call child_handler unconditionally >- >-+ child_cleaner(); >-+ >- do_sighup_restart() if defined $got_sighup; >- >- for (my $i = keys %children; $i < $childlimit; $i++) { >-@@ -2523,7 +2526,8 @@ >- my ($sig) = @_; >- >- # do NOT call syslog here unless the child's pid is in our list of known >-- # children. This is due to syslog-ng brokenness -- bugs 3625, 4237. >-+ # children. This is due to syslog-ng brokenness -- bugs 3625, 4237; >-+ # see also bug 6745. >- >- # clean up any children which have exited >- for (;;) { >-@@ -2534,12 +2538,23 @@ >- # >- my $pid = waitpid(-1, WNOHANG); >- last if !$pid || $pid == -1; >-- my $child_stat = $?; >-+ push(@children_exited, [$pid, $?, $sig, time]); >-+ } >- >-- if (!defined $children{$pid}) { >-- # ignore this child; we didn't realise we'd forked it. bug 4237 >-- next; >-- } >-+ $SIG{CHLD} = \&child_handler; # reset as necessary, should be at end >-+} >-+ >-+# takes care of dead children, as noted by a child_handler() >-+# called in a main program flow (not from a signal handler) >-+# >-+sub child_cleaner { >-+ while (@children_exited) { >-+ my $tuple = shift(@children_exited); >-+ next if !$tuple; # just in case >-+ my($pid, $child_stat, $sig, $timestamp) = @$tuple; >-+ >-+ # ignore this child if we didn't realise we'd forked it. bug 4237 >-+ next if !defined $children{$pid}; >- >- # remove them from our child listing >- delete $children{$pid}; >-@@ -2550,15 +2565,10 @@ >- my $sock = $backchannel->get_socket_for_child($pid); >- if ($sock) { $sock->close(); } >- } >-- >-- unless ($Mail::SpamAssassin::Logger::LOG_SA{INHIBIT_LOGGING_IN_SIGCHLD_HANDLER}) { >-- info("spamd: handled cleanup of child pid [%s]%s: %s", >-- $pid, (defined $sig ? " due to SIG$sig" : ""), >-- exit_status_str($child_stat,0)); >-- } >-+ info("spamd: handled cleanup of child pid [%s]%s: %s", >-+ $pid, (defined $sig ? " due to SIG$sig" : ""), >-+ exit_status_str($child_stat,0)); >- } >-- >-- $SIG{CHLD} = \&child_handler; # reset as necessary, should be at end >- } >- >- sub restart_handler { >diff -ruN /usr/ports/mail/p5-Mail-SpamAssassin/pkg-deinstall ./pkg-deinstall >--- /usr/ports/mail/p5-Mail-SpamAssassin/pkg-deinstall 2014-01-23 00:52:06.000000000 +0900 >+++ ./pkg-deinstall 2014-02-14 16:25:23.000000000 +0900 >@@ -4,11 +4,14 @@ > exit 0 > fi > >+USER=spamd >+ > if [ -d /var/db/spamassassin ]; then > echo "To delete /var/db/spamassassin, use 'rm -rf /var/db/spamassassin'" > fi >- >-USER=spamd >+if [ -d /var/spool/spamd ]; then >+ echo "'Rmuser ${USER}' /var/spool/spamd disappears when this command is executed." >+fi > > if pw usershow "${USER}" 2>/dev/null 1>&2; then > echo "To delete ${USER} user permanently, use 'rmuser ${USER}'" >diff -ruN /usr/ports/mail/p5-Mail-SpamAssassin/pkg-descr ./pkg-descr >--- /usr/ports/mail/p5-Mail-SpamAssassin/pkg-descr 2014-01-23 00:44:51.000000000 +0900 >+++ ./pkg-descr 2013-10-03 17:01:51.000000000 +0900 >@@ -11,4 +11,4 @@ > Additional drop-in rule sets are available at > http://wiki.apache.org/spamassassin/CustomRulesets > >-WWW: http://spamassassin.apache.org/ >+WWW: http://spamassassin.apache.org/ >diff -ruN /usr/ports/mail/p5-Mail-SpamAssassin/pkg-plist ./pkg-plist >--- /usr/ports/mail/p5-Mail-SpamAssassin/pkg-plist 2014-01-10 18:36:12.000000000 +0900 >+++ ./pkg-plist 2014-02-14 16:18:16.000000000 +0900 >@@ -225,4 +225,3 @@ > @dirrmtry etc/mail/spamassassin > @dirrmtry etc/mail > @unexec rm -rf /var/run/spamd >-@unexec rm -rf /var/spool/spamd
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 186756
: 140242 |
140243
|
140244
|
140245
|
140246