Date: Fri, 14 Feb 2014 17:02:31 +0900 From: Takefu <takefu@airport.fm> To: freebsd-gnats-submit@freebsd.org Cc: perl@freebsd.org Subject: ports/186756: [UPDATE] mail/p5-Mail-SpamAssassin: update to 3.4.0 Message-ID: <52FDCD97.1070409@airport.fm> Resent-Message-ID: <201402140810.s1E8A0uo098296@freefall.freebsd.org>
next in thread | raw e-mail | index | archive | help
>Number: 186756 >Category: ports >Synopsis: [UPDATE] mail/p5-Mail-SpamAssassin: update to 3.4.0 >Confidential: no >Severity: non-critical >Priority: low >Responsible: freebsd-ports-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: update >Submitter-Id: current-users >Arrival-Date: Fri Feb 14 08:10:00 UTC 2014 >Closed-Date: >Last-Modified: >Originator: Takefu >Release: FreeBSD 8.4-RELEASE-p7 i386 >Organization: FOX Amateur Radio Club >Environment: System: FreeBSD RELENG84-ix86.localIPv4.airport.fm 8.4-RELEASE-p7 FreeBSD 8.4-RELEASE-p7 #0: Wed Jan 15 09:30:02 JST 2014 >Description: - Update to 3.4.0 - fix Package making failure is done. PR close. ports/180498 ports/182684 Port maintainer (perl@FreeBSD.org) is cc'd. Generated with FreeBSD Port Tools 0.99_11 (mode: update, diff: ports) >How-To-Repeat: >Fix: --- p5-Mail-SpamAssassin-3.4.0.patch begins here --- 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 --- p5-Mail-SpamAssassin-3.4.0.patch ends here --- >Release-Note: >Audit-Trail: >Unformatted:
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?52FDCD97.1070409>