You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by qu...@apache.org on 2005/01/29 01:07:26 UTC
svn commit: r148958 - in spamassassin/trunk: lib/Mail/SpamAssassin lib/Mail/SpamAssassin/Plugin rules
Author: quinlan
Date: Fri Jan 28 16:07:26 2005
New Revision: 148958
URL: http://svn.apache.org/viewcvs?view=rev&rev=148958
Log:
move DCC code to DCC plugin
Added:
spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DCC.pm
spamassassin/trunk/rules/25_dcc.cf
Modified:
spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm
spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm
spamassassin/trunk/rules/20_net_tests.cf
spamassassin/trunk/rules/init.pre
Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
Url: http://svn.apache.org/viewcvs/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm?view=diff&rev=148958&p1=spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm&r1=148957&p2=spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm&r2=148958
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm Fri Jan 28 16:07:26 2005
@@ -991,66 +991,6 @@
=over 4
-=item use_dcc ( 0 | 1 ) (default: 1)
-
-Whether to use DCC, if it is available. DCC (Distributed Checksum
-Clearinghouse) is a system similar to Razor.
-
-=cut
-
- push (@cmds, {
- setting => 'use_dcc',
- default => 1,
- type => $CONF_TYPE_BOOL
- });
-
-=item dcc_timeout n (default: 10)
-
-How many seconds you wait for DCC to complete, before scanning continues
-without the DCC results.
-
-=cut
-
- push (@cmds, {
- setting => 'dcc_timeout',
- default => 10,
- type => $CONF_TYPE_NUMERIC
- });
-
-=item dcc_body_max NUMBER
-
-=item dcc_fuz1_max NUMBER
-
-=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).
-
-The default is C<999999> for all these options.
-
-=cut
-
- push (@cmds, {
- setting => 'dcc_body_max',
- default => 999999,
- type => $CONF_TYPE_NUMERIC
- },
- {
- setting => 'dcc_fuz1_max',
- default => 999999,
- type => $CONF_TYPE_NUMERIC
- },
- {
- setting => 'dcc_fuz2_max',
- default => 999999,
- type => $CONF_TYPE_NUMERIC
- });
-
-
=item use_pyzor ( 0 | 1 ) (default: 1)
Whether to use Pyzor, if it is available.
@@ -2527,72 +2467,6 @@
is_admin => 1,
default => undef,
type => $CONF_TYPE_STRING
- });
-
-=item dcc_home STRING
-
-This option tells SpamAssassin specifically where to find the dcc homedir.
-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>, it will use that interface that instead of C<dccproc>.
-
-=cut
-
- push (@cmds, {
- setting => 'dcc_home',
- is_admin => 1,
- type => $CONF_TYPE_STRING
- });
-
-=item dcc_dccifd_path STRING
-
-This option tells SpamAssassin specifically where to find the dccifd socket.
-If C<dcc_dccifd_path> is not specified, it will default to looking in C<dcc_home>
-If a C<dccifd> socket is found, it will use it instead of C<dccproc>.
-
-=cut
-
- push (@cmds, {
- setting => 'dcc_dccifd_path',
- is_admin => 1,
- type => $CONF_TYPE_STRING
- });
-
-=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.
-
-=cut
-
- push (@cmds, {
- setting => 'dcc_path',
- is_admin => 1,
- default => undef,
- type => $CONF_TYPE_STRING
- });
-
-=item dcc_options options
-
-Specify additional options to the dccproc(8) command. Please note that only
-[A-Z -] is allowed (security).
-
-The default is C<-R>.
-
-=cut
-
- push (@cmds, {
- setting => 'dcc_options',
- is_admin => 1,
- default => '-R',
- code => sub {
- my ($self, $key, $value, $line) = @_;
- if ($value !~ /^([A-Z -]+)/) { return $INVALID_VALUE; }
- $self->{dcc_options} = $1;
- }
});
=item use_auto_whitelist ( 0 | 1 ) (default: 1)
Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm
Url: http://svn.apache.org/viewcvs/spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm?view=diff&rev=148958&p1=spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm&r1=148957&p2=spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm&r2=148958
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm Fri Jan 28 16:07:26 2005
@@ -387,317 +387,6 @@
###########################################################################
-sub is_dccifd_available {
- my ($self) = @_;
-
- if ($self->{main}->{local_tests_only}) {
- dbg("dcc: local tests only, ignoring dccifd");
- return 0;
- }
-
- my $dcchome = $self->{conf}->{dcc_home} || '';
- my $dccifd = $self->{conf}->{dcc_dccifd_path} || '';
-
- if (!$dccifd && ($dcchome && -S "$dcchome/dccifd")) {
- $dccifd = "$dcchome/dccifd";
- }
-
- unless ($dccifd && -S $dccifd && -w _ && -r _ ) {
- dbg("dcc: dccifd is not available: no r/w dccifd socket found.");
- return 0;
- }
-
- # Remember any found dccifd socket
- $self->{conf}->{dcc_dccifd_path} = $dccifd;
-
- dbg("dcc: dccifd is available: ".$self->{conf}->{dcc_dccifd_path});
- return 1;
-}
-
-sub is_dcc_available {
- my ($self) = @_;
-
- if ($self->{main}->{local_tests_only}) {
- dbg("dcc: local tests only, ignoring DCC");
- return 0;
- }
- if (!$self->{conf}->{use_dcc}) { return 0; }
-
- my $dcchome = $self->{conf}->{dcc_home} || '';
- my $dccproc = $self->{conf}->{dcc_path} || '';
-
- if (!$dccproc && ($dcchome && -x "$dcchome/bin/dccproc")) {
- $dccproc = "$dcchome/bin/dccproc";
- }
- unless ($dccproc) {
- $dccproc = Mail::SpamAssassin::Util::find_executable_in_env_path('dccproc');
- }
-
- unless ($dccproc && -x $dccproc) {
- dbg("dcc: DCC is not available: no executable dccproc found.");
- return 0;
- }
-
- # Remember any found dccproc
- $self->{conf}->{dcc_path} = $dccproc;
-
- dbg("dcc: DCC is available: ".$self->{conf}->{dcc_path});
- return 1;
-}
-
-sub dccifd_lookup {
- my ($self, $fulltext) = @_;
- my $response = "";
- my %count;
- my $left;
- my $right;
- my $timeout=$self->{conf}->{dcc_timeout};
- my $sockpath;
-
- $count{body} = 0;
- $count{fuz1} = 0;
- $count{fuz2} = 0;
-
- if ($self->{main}->{local_tests_only}) {
- dbg("dcc: local tests only, ignoring dccifd");
- return 0;
- }
-
- if ($$fulltext eq '') {
- dbg("dcc: empty message, ignoring dccifd");
- return 0;
- }
-
- if ( ! $self->{conf}->{dcc_home} ) {
- dbg("dcc: dcc_home not defined, should not get here");
- return 0;
- }
-
- $sockpath = $self->{conf}->{dcc_dccifd_path};
- if ( ! -S $sockpath || ! -w _ || ! -r _ ) {
- dbg("dcc: dccifd not a socket, should not get here");
- return 0;
- }
-
- $self->enter_helper_run_mode();
- my $oldalarm = 0;
-
- eval {
- # safe to use $SIG{ALRM} here instead of Util::trap_sigalrm_fully(),
- # since there are no killer regexp hang dangers here
- local $SIG{ALRM} = sub { die "__alarm__\n" };
-
- $oldalarm = alarm $timeout;
-
- my $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM,
- Peer => $sockpath) || dbg("dcc: failed to open socket") && die;
-
- # send the options and other parameters to the daemon
- $sock->print("header\n") || dbg("dcc: failed write") && die; # options
- $sock->print("0.0.0.0\n") || dbg("dcc: failed write") && die; #client
- $sock->print("\n") || dbg("dcc: failed write") && die; #HELO value
- $sock->print("\n") || dbg("dcc: failed write") && die; #sender
- $sock->print("unknown\r\n") || dbg("dcc: failed write") && die; # recipients
- $sock->print("\n") || dbg("dcc: failed write") && die; # recipients
-
- $sock->print($$fulltext);
-
- $sock->shutdown(1) || dbg("dcc: failed socket shutdown: $!") && die;
-
- $sock->getline() || dbg("dcc: failed read status") && die;
- $sock->getline() || dbg("dcc: failed read multistatus") && die;
-
- my @null = $sock->getlines();
- if ( $#null == -1 ) {
- dbg("dcc: failed read header");
- die;
- }
-
- # 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;
- }
-
- dbg("dcc: dccifd got response: $response");
- alarm $oldalarm;
- };
-
- # do NOT reinstate $oldalarm here; we may already have done that in
- # the success case. leave it to the error handler below
- my $err = $@;
- $self->leave_helper_run_mode();
-
- if ($err) {
- alarm $oldalarm;
- $response = undef;
- if ($err =~ /__alarm__/) {
- dbg("dcc: dccifd check timed out after $timeout secs.");
- return 0;
- } else {
- warn("dcc: dccifd -> check skipped: $! $err");
- return 0;
- }
- }
-
- if (!defined $response || $response !~ /^X-DCC/) {
- dbg("dcc: dccifd check failed - no X-DCC returned: $response");
- return 0;
- }
-
- if ($response =~ /^X-DCC-(.*)-Metrics: (.*)$/) {
- $self->{tag_data}->{DCCB} = $1;
- $self->{tag_data}->{DCCR} = $2;
- }
-
- $response =~ s/many/999999/ig;
- $response =~ s/ok\d?/0/ig;
-
- if ($response =~ /Body=(\d+)/) {
- $count{body} = $1+0;
- }
- if ($response =~ /Fuz1=(\d+)/) {
- $count{fuz1} = $1+0;
- }
- if ($response =~ /Fuz2=(\d+)/) {
- $count{fuz2} = $1+0;
- }
-
- if ($count{body} >= $self->{conf}->{dcc_body_max} || $count{fuz1} >= $self->{conf}->{dcc_fuz1_max} || $count{fuz2} >= $self->{conf}->{dcc_fuz2_max}) {
- dbg("dcc: listed! BODY: $count{body} of $self->{conf}->{dcc_body_max} FUZ1: $count{fuz1} of $self->{conf}->{dcc_fuz1_max} FUZ2: $count{fuz2} of $self->{conf}->{dcc_fuz2_max}");
- return 1;
- }
-
- return 0;
-}
-
-sub dcc_lookup {
- my ($self, $fulltext) = @_;
- my $response = undef;
- my %count;
- my $timeout=$self->{conf}->{dcc_timeout};
-
- $count{body} = 0;
- $count{fuz1} = 0;
- $count{fuz2} = 0;
-
- if ($self->{main}->{local_tests_only}) {
- dbg("dcc: local tests only, ignoring DCC");
- return 0;
- }
- if (!$self->{conf}->{use_dcc}) { return 0; }
-
- $self->enter_helper_run_mode();
-
- # use a temp file here -- open2() is unreliable, buffering-wise,
- # under spamd. :(
- my $tmpf = $self->create_fulltext_tmpfile($fulltext);
- my $oldalarm = 0;
-
- eval {
- # safe to use $SIG{ALRM} here instead of Util::trap_sigalrm_fully(),
- # since there are no killer regexp hang dangers here
- local $SIG{ALRM} = sub { die "__alarm__\n" };
- local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
-
- $oldalarm = alarm $timeout;
-
- # Note: not really tainted, these both come from system conf file.
- my $path = Mail::SpamAssassin::Util::untaint_file_path ($self->{conf}->{dcc_path});
-
- my $opts = '';
- if ( $self->{conf}->{dcc_options} =~ /^([^\;\'\"\0]+)$/ ) {
- $opts = $1;
- }
-
- dbg("dcc: command: ".join(' ', $path, "-H", $opts, "< '$tmpf'", "2>&1"));
-
- # my $pid = open(DCC, join(' ', $path, "-H", $opts, "< '$tmpf'", "2>&1", '|')) || die "$!\n";
- my $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*DCC,
- $tmpf, 1, $path, "-H", split(' ', $opts));
- $pid or die "$!\n";
-
- my @null = <DCC>;
- close DCC;
-
- if ( $#null == -1 ) {
- dbg("dcc: failed read header");
- die;
- }
-
- # 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;
- }
-
- unless (defined($response)) {
- die("dcc: no response\n"); # yes, this is possible
- }
-
- dbg("dcc: got response: $response");
-
- # note: this must be called BEFORE leave_helper_run_mode()
- # $self->cleanup_kids($pid);
- alarm $oldalarm;
- };
-
- # do NOT reinstate $oldalarm here; we may already have done that in
- # the success case. leave it to the error handler below
- my $err = $@;
- $self->leave_helper_run_mode();
-
- if ($err) {
- alarm $oldalarm;
- if ($err =~ /^__alarm__$/) {
- dbg("dcc: check timed out after $timeout secs.");
- } elsif ($err =~ /^__brokenpipe__$/) {
- dbg("dcc: check failed: broken pipe");
- } elsif ($err eq "no response\n") {
- dbg("dcc: check failed: no response");
- } else {
- warn("dcc: check failed: $err\n");
- }
- return 0;
- }
-
- if (!defined($response) || $response !~ /^X-DCC/) {
- dbg("dcc: check failed: no X-DCC returned (did you create a map file?): $response");
- return 0;
- }
-
- if ($response =~ /^X-DCC-(.*)-Metrics: (.*)$/) {
- $self->{tag_data}->{DCCB} = $1;
- $self->{tag_data}->{DCCR} = $2;
- }
-
- $response =~ s/many/999999/ig;
- $response =~ s/ok\d?/0/ig;
-
- if ($response =~ /Body=(\d+)/) {
- $count{body} = $1+0;
- }
- if ($response =~ /Fuz1=(\d+)/) {
- $count{fuz1} = $1+0;
- }
- if ($response =~ /Fuz2=(\d+)/) {
- $count{fuz2} = $1+0;
- }
-
- if ($count{body} >= $self->{conf}->{dcc_body_max} || $count{fuz1} >= $self->{conf}->{dcc_fuz1_max} || $count{fuz2} >= $self->{conf}->{dcc_fuz2_max}) {
- dbg("dcc: listed! BODY: $count{body} of $self->{conf}->{dcc_body_max} FUZ1: $count{fuz1} of $self->{conf}->{dcc_fuz1_max} FUZ2: $count{fuz2} of $self->{conf}->{dcc_fuz2_max}");
- return 1;
- }
-
- return 0;
-}
-
sub is_pyzor_available {
my ($self) = @_;
Modified: spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm
Url: http://svn.apache.org/viewcvs/spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm?view=diff&rev=148958&p1=spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm&r1=148957&p2=spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm&r2=148958
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm Fri Jan 28 16:07:26 2005
@@ -2627,28 +2627,6 @@
return $self->pyzor_lookup($full);
}
-sub check_dcc {
- my ($self, $full) = @_;
- my $have_dccifd = $self->is_dccifd_available();
-
- return 0 unless ($have_dccifd || $self->is_dcc_available() );
- return 0 if ($self->{already_checked_dcc});
-
- $self->{already_checked_dcc} = 1;
-
- # First check if there's already a X-DCC header with value of "bulk"
- # and short-circuit if there is -- someone upstream might already have
- # checked DCC for us.
- return 1 if grep(/^X-DCC-(?:[^:]{1,80}-)?Metrics:/ && /bulk/, $self->{msg}->get_all_headers());
-
- if ($have_dccifd) {
- return $self->dccifd_lookup($full);
- }
- else {
- return $self->dcc_lookup($full);
- }
-}
-
###########################################################################
sub check_for_fake_aol_relay_in_rcvd {
Added: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DCC.pm
Url: http://svn.apache.org/viewcvs/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DCC.pm?view=auto&rev=148958
==============================================================================
--- (empty file)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DCC.pm Fri Jan 28 16:07:26 2005
@@ -0,0 +1,547 @@
+=head1 NAME
+
+Mail::SpamAssassin::Plugin::DCC - perform DCC check of messages
+
+The DCC or Distributed Checksum Clearinghouse is a system of servers
+collecting and counting checksums of millions of mail messages. The
+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.
+
+See http://www.rhyolite.com/anti-spam/dcc/ for more information about
+DCC.
+
+=head1 SYNOPSIS
+
+ loadplugin Mail::SpamAssassin::Plugin::DCC
+
+=over 4
+
+=cut
+
+# <@LICENSE>
+# Copyright 2004 Apache Software Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# </...@LICENSE>
+
+package Mail::SpamAssassin::Plugin::DCC;
+
+# Make the main dbg() accessible in our package w/o an extra function
+*dbg=\&Mail::SpamAssassin::Plugin::dbg;
+
+use Mail::SpamAssassin::Plugin;
+use IO::Socket;
+use strict;
+use warnings;
+use bytes;
+
+use vars qw(@ISA);
+@ISA = qw(Mail::SpamAssassin::Plugin);
+
+sub new {
+ my $class = shift;
+ my $mailsaobject = shift;
+
+ $class = ref($class) || $class;
+ my $self = $class->SUPER::new($mailsaobject);
+ bless ($self, $class);
+
+ # DCC is not available
+ if ($mailsaobject->{local_tests_only}) {
+ $self->{dcc_available} = 0;
+ dbg("dcc: local tests only, disabling DCC");
+ }
+ else {
+ $self->{dcc_available} = 1;
+ dbg("dcc: network tests on, attempting DCC");
+ }
+
+ $self->register_eval_rule("check_dcc");
+
+ $self->set_config($mailsaobject->{conf});
+
+ return $self;
+}
+
+sub set_config {
+ my($self, $conf) = @_;
+ my @cmds = ();
+
+=head1 USER OPTIONS
+
+=item use_dcc (0|1) (default: 1)
+
+Whether to use DCC, if it is available. DCC is used to check message
+signatures over the network against the Distributed Checksum Clearinghouse.
+
+=cut
+
+ push(@cmds, {
+ setting => 'use_dcc',
+ default => 1,
+ type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL,
+ });
+
+=item dcc_timeout n (default: 10)
+
+How many seconds you wait for DCC to complete, before scanning continues
+without the DCC results.
+
+=cut
+
+ push (@cmds, {
+ setting => 'dcc_timeout',
+ default => 10,
+ type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
+ });
+
+=item dcc_body_max NUMBER
+
+=item dcc_fuz1_max NUMBER
+
+=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).
+
+The default is C<999999> for all these options.
+
+=cut
+
+ push (@cmds, {
+ setting => 'dcc_body_max',
+ default => 999999,
+ type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
+ },
+ {
+ setting => 'dcc_fuz1_max',
+ default => 999999,
+ type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
+ },
+ {
+ setting => 'dcc_fuz2_max',
+ default => 999999,
+ type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
+ });
+
+=head1 ADMINISTRATOR OPTIONS
+
+=item dcc_home STRING
+
+This option tells SpamAssassin specifically where to find the dcc homedir.
+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>, it will use that
+interface that instead of C<dccproc>.
+
+=cut
+
+ push (@cmds, {
+ setting => 'dcc_home',
+ is_admin => 1,
+ type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
+ });
+
+=item dcc_dccifd_path STRING
+
+This option tells SpamAssassin specifically where to find the dccifd socket.
+If C<dcc_dccifd_path> is not specified, it will default to looking in
+C<dcc_home> If a C<dccifd> socket is found, it will use it instead of
+C<dccproc>.
+
+=cut
+
+ push (@cmds, {
+ setting => 'dcc_dccifd_path',
+ is_admin => 1,
+ type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
+ });
+
+=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.
+
+=cut
+
+ push (@cmds, {
+ setting => 'dcc_path',
+ is_admin => 1,
+ default => undef,
+ type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING
+ });
+
+=item dcc_options options
+
+Specify additional options to the dccproc(8) command. Please note that only
+[A-Z -] is allowed for security reasons.
+
+The default is C<-R>.
+
+=cut
+
+ push (@cmds, {
+ setting => 'dcc_options',
+ is_admin => 1,
+ default => '-R',
+ code => sub {
+ my ($self, $key, $value, $line) = @_;
+ if ($value !~ /^([A-Z -]+)/) {
+ return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+ }
+ $self->{dcc_options} = $1;
+ }
+ });
+
+ $conf->{parser}->register_commands(\@cmds);
+}
+
+sub is_dccifd_available {
+ my ($self) = @_;
+
+ my $dcchome = $self->{main}->{conf}->{dcc_home} || '';
+ my $dccifd = $self->{main}->{conf}->{dcc_dccifd_path} || '';
+
+ if (!$dccifd && ($dcchome && -S "$dcchome/dccifd")) {
+ $dccifd = "$dcchome/dccifd";
+ }
+
+ unless ($dccifd && -S $dccifd && -w _ && -r _) {
+ dbg("dcc: dccifd is not available: no r/w dccifd socket found");
+ return 0;
+ }
+
+ # remember any found dccifd socket
+ $self->{main}->{conf}->{dcc_dccifd_path} = $dccifd;
+
+ dbg("dcc: dccifd is available: " . $self->{main}->{conf}->{dcc_dccifd_path});
+ return 1;
+}
+
+sub is_dccproc_available {
+ my ($self) = @_;
+
+ my $dcchome = $self->{main}->{conf}->{dcc_home} || '';
+ my $dccproc = $self->{main}->{conf}->{dcc_path} || '';
+
+ if (!$dccproc && ($dcchome && -x "$dcchome/bin/dccproc")) {
+ $dccproc = "$dcchome/bin/dccproc";
+ }
+ unless ($dccproc) {
+ $dccproc = Mail::SpamAssassin::Util::find_executable_in_env_path('dccproc');
+ }
+
+ unless ($dccproc && -x $dccproc) {
+ dbg("dcc: dccproc is not available: no executable dccproc found");
+ return 0;
+ }
+
+ # remember any found dccproc
+ $self->{main}->{conf}->{dcc_path} = $dccproc;
+
+ dbg("dcc: dccifd is available: " . $self->{main}->{conf}->{dcc_path});
+ return 1;
+}
+
+sub get_dcc_interface {
+ my ($self) = @_;
+
+ if (!$self->{main}->{conf}->{use_dcc}) {
+ dbg("dcc: use_dcc option not enabled, disabling DCC");
+ $self->{dcc_interface} = "disabled";
+ $self->{dcc_available} = 0;
+ }
+ elsif ($self->is_dccifd_available()) {
+ $self->{dcc_interface} = "dccifd";
+ }
+ elsif ($self->is_dccproc_available()) {
+ $self->{dcc_interface} = "dccproc";
+ }
+ else {
+ dbg("dcc: no dccifd or dccproc found, disabling DCC");
+ $self->{dcc_interface} = "none";
+ $self->{dcc_available} = 0;
+ }
+}
+
+sub check_dcc {
+ my ($self, $permsgstatus, $full) = @_;
+
+ $self->get_dcc_interface() unless $self->{dcc_interface};
+ return 0 unless $self->{dcc_available};
+
+ # First check if there's already a X-DCC header with value of "bulk"
+ # and short-circuit if there is -- someone upstream might already have
+ # checked DCC for us.
+ return 1 if (grep(/^X-DCC-(?:[^:]{1,80}-)?Metrics:.*bulk/m, $permsgstatus->get('ALL')));
+
+ if ($$full eq '') {
+ dbg("dcc: empty message, skipping dcc check");
+ return 0;
+ }
+
+ if ($self->{dcc_interface} eq "dccifd") {
+ return $self->dccifd_lookup($permsgstatus, $full);
+ }
+ else {
+ return $self->dccproc_lookup($permsgstatus, $full);
+ }
+ return 0;
+}
+
+sub dccifd_lookup {
+ my ($self, $permsgstatus, $fulltext) = @_;
+ my $response = "";
+ my %count;
+ my $left;
+ my $right;
+ my $timeout = $self->{main}->{conf}->{dcc_timeout};
+ my $sockpath = $self->{main}->{conf}->{dcc_dccifd_path};
+
+ $count{body} = 0;
+ $count{fuz1} = 0;
+ $count{fuz2} = 0;
+
+ $permsgstatus->enter_helper_run_mode();
+
+ my $oldalarm = 0;
+
+ eval {
+ # safe to use $SIG{ALRM} here instead of Util::trap_sigalrm_fully(),
+ # since there are no killer regexp hang dangers here
+ local $SIG{ALRM} = sub { die "__alarm__\n" };
+
+ $oldalarm = alarm $timeout;
+
+ my $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM,
+ Peer => $sockpath) || dbg("dcc: failed to open socket") && die;
+
+ # send the options and other parameters to the daemon
+ $sock->print("header\n") || dbg("dcc: failed write") && die; # options
+ $sock->print("0.0.0.0\n") || dbg("dcc: failed write") && die; # client
+ $sock->print("\n") || dbg("dcc: failed write") && die; # HELO value
+ $sock->print("\n") || dbg("dcc: failed write") && die; # sender
+ $sock->print("unknown\r\n") || dbg("dcc: failed write") && die; # recipients
+ $sock->print("\n") || dbg("dcc: failed write") && die; # recipients
+
+ $sock->print($$fulltext);
+
+ $sock->shutdown(1) || dbg("dcc: failed socket shutdown: $!") && die;
+
+ $sock->getline() || dbg("dcc: failed read status") && die;
+ $sock->getline() || dbg("dcc: failed read multistatus") && die;
+
+ my @null = $sock->getlines();
+ if (!@null) {
+ dbg("dcc: failed read header");
+ die;
+ }
+
+ # 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;
+ }
+
+ dbg("dcc: dccifd got response: $response");
+
+ alarm $oldalarm;
+ };
+
+ # do NOT reinstate $oldalarm here; we may already have done that in
+ # the success case. leave it to the error handler below
+ my $err = $@;
+ $permsgstatus->leave_helper_run_mode();
+
+ if ($err) {
+ alarm $oldalarm;
+ $response = undef;
+ if ($err =~ /__alarm__/) {
+ dbg("dcc: dccifd check timed out after $timeout secs.");
+ return 0;
+ } else {
+ warn("dcc: dccifd -> check skipped: $! $err");
+ return 0;
+ }
+ }
+
+ if (!defined $response || $response !~ /^X-DCC/) {
+ dbg("dcc: dccifd check failed - no X-DCC returned: $response");
+ return 0;
+ }
+
+ if ($response =~ /^X-DCC-(.*)-Metrics: (.*)$/) {
+ $permsgstatus->{tag_data}->{DCCB} = $1;
+ $permsgstatus->{tag_data}->{DCCR} = $2;
+ }
+
+ $response =~ s/many/999999/ig;
+ $response =~ s/ok\d?/0/ig;
+
+ if ($response =~ /Body=(\d+)/) {
+ $count{body} = $1+0;
+ }
+ if ($response =~ /Fuz1=(\d+)/) {
+ $count{fuz1} = $1+0;
+ }
+ if ($response =~ /Fuz2=(\d+)/) {
+ $count{fuz2} = $1+0;
+ }
+
+ if ($count{body} >= $self->{main}->{conf}->{dcc_body_max} ||
+ $count{fuz1} >= $self->{main}->{conf}->{dcc_fuz1_max} ||
+ $count{fuz2} >= $self->{main}->{conf}->{dcc_fuz2_max})
+ {
+ dbg(sprintf("dcc: listed: BODY=%s/%s FUZ1=%s/%s FUZ2=%s/%s",
+ $count{body}, $self->{main}->{conf}->{dcc_body_max},
+ $count{fuz1}, $self->{main}->{conf}->{dcc_fuz1_max},
+ $count{fuz2}, $self->{main}->{conf}->{dcc_fuz2_max}));
+ return 1;
+ }
+
+ return 0;
+}
+
+sub dccproc_lookup {
+ my ($self, $permsgstatus, $fulltext) = @_;
+ my $response = undef;
+ my %count;
+ my $timeout = $self->{main}->{conf}->{dcc_timeout};
+
+ $count{body} = 0;
+ $count{fuz1} = 0;
+ $count{fuz2} = 0;
+
+ $permsgstatus->enter_helper_run_mode();
+
+ # use a temp file here -- open2() is unreliable, buffering-wise, under spamd
+ my $tmpf = $permsgstatus->create_fulltext_tmpfile($fulltext);
+ my $oldalarm = 0;
+
+ eval {
+ # safe to use $SIG{ALRM} here instead of Util::trap_sigalrm_fully(),
+ # since there are no killer regexp hang dangers here
+ local $SIG{ALRM} = sub { die "__alarm__\n" };
+ local $SIG{PIPE} = sub { die "__brokenpipe__\n" };
+
+ $oldalarm = alarm $timeout;
+
+ # Note: not really tainted, these both come from system conf file.
+ my $path = Mail::SpamAssassin::Util::untaint_file_path($self->{main}->{conf}->{dcc_path});
+
+ my $opts = '';
+ if ($self->{main}->{conf}->{dcc_options} =~ /^([^\;\'\"\0]+)$/) {
+ $opts = $1;
+ }
+
+ dbg("dcc: opening pipe: " . join(' ', $path, "-H", $opts, "< $tmpf"));
+
+ my $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*DCC,
+ $tmpf, 1, $path, "-H", split(' ', $opts));
+ $pid or die "$!\n";
+
+ my @null = <DCC>;
+ close DCC;
+ if (!@null) {
+ dbg("dcc: failed read header");
+ die;
+ }
+
+ # 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;
+ }
+
+ unless (defined($response)) {
+ die("dcc: no response\n"); # yes, this is possible
+ }
+
+ dbg("dcc: got response: $response");
+
+ # note: this must be called BEFORE leave_helper_run_mode()
+ # $self->cleanup_kids($pid);
+ alarm $oldalarm;
+ };
+
+ # do NOT reinstate $oldalarm here; we may already have done that in
+ # the success case. leave it to the error handler below
+ my $err = $@;
+ $permsgstatus->leave_helper_run_mode();
+
+ if ($err) {
+ alarm $oldalarm;
+ if ($err =~ /^__alarm__$/) {
+ dbg("dcc: check timed out after $timeout secs.");
+ } elsif ($err =~ /^__brokenpipe__$/) {
+ dbg("dcc: check failed: broken pipe");
+ } elsif ($err eq "no response\n") {
+ dbg("dcc: check failed: no response");
+ } else {
+ warn("dcc: check failed: $err\n");
+ }
+ return 0;
+ }
+
+ if (!defined($response) || $response !~ /^X-DCC/) {
+ dbg("dcc: check failed: no X-DCC returned (did you create a map file?): $response");
+ return 0;
+ }
+
+ if ($response =~ /^X-DCC-(.*)-Metrics: (.*)$/) {
+ $permsgstatus->{tag_data}->{DCCB} = $1;
+ $permsgstatus->{tag_data}->{DCCR} = $2;
+ }
+
+ $response =~ s/many/999999/ig;
+ $response =~ s/ok\d?/0/ig;
+
+ if ($response =~ /Body=(\d+)/) {
+ $count{body} = $1+0;
+ }
+ if ($response =~ /Fuz1=(\d+)/) {
+ $count{fuz1} = $1+0;
+ }
+ if ($response =~ /Fuz2=(\d+)/) {
+ $count{fuz2} = $1+0;
+ }
+
+ if ($count{body} >= $self->{main}->{conf}->{dcc_body_max} ||
+ $count{fuz1} >= $self->{main}->{conf}->{dcc_fuz1_max} ||
+ $count{fuz2} >= $self->{main}->{conf}->{dcc_fuz2_max})
+ {
+ dbg(sprintf("dcc: listed: BODY=%s/%s FUZ1=%s/%s FUZ2=%s/%s",
+ $count{body}, $self->{main}->{conf}->{dcc_body_max},
+ $count{fuz1}, $self->{main}->{conf}->{dcc_fuz1_max},
+ $count{fuz2}, $self->{main}->{conf}->{dcc_fuz2_max}));
+ return 1;
+ }
+
+ return 0;
+}
Modified: spamassassin/trunk/rules/20_net_tests.cf
Url: http://svn.apache.org/viewcvs/spamassassin/trunk/rules/20_net_tests.cf?view=diff&rev=148958&p1=spamassassin/trunk/rules/20_net_tests.cf&r1=148957&p2=spamassassin/trunk/rules/20_net_tests.cf&r2=148958
==============================================================================
--- spamassassin/trunk/rules/20_net_tests.cf (original)
+++ spamassassin/trunk/rules/20_net_tests.cf Fri Jan 28 16:07:26 2005
@@ -34,11 +34,6 @@
# ---------------------------------------------------------------------------
# Message digest tests
-full DCC_CHECK eval:check_dcc()
-describe DCC_CHECK Listed in DCC (http://rhyolite.com/anti-spam/dcc/)
-tflags DCC_CHECK net
-#reuse DCC_CHECK
-
full PYZOR_CHECK eval:check_pyzor()
describe PYZOR_CHECK Listed in Pyzor (http://pyzor.sf.net/)
tflags PYZOR_CHECK net
Added: spamassassin/trunk/rules/25_dcc.cf
Url: http://svn.apache.org/viewcvs/spamassassin/trunk/rules/25_dcc.cf?view=auto&rev=148958
==============================================================================
--- (empty file)
+++ spamassassin/trunk/rules/25_dcc.cf Fri Jan 28 16:07:26 2005
@@ -0,0 +1,8 @@
+ifplugin Mail::SpamAssassin::Plugin::DCC
+
+full DCC_CHECK eval:check_dcc()
+describe DCC_CHECK Listed in DCC (http://rhyolite.com/anti-spam/dcc/)
+tflags DCC_CHECK net
+#reuse DCC_CHECK
+
+endif
Modified: spamassassin/trunk/rules/init.pre
Url: http://svn.apache.org/viewcvs/spamassassin/trunk/rules/init.pre?view=diff&rev=148958&p1=spamassassin/trunk/rules/init.pre&r1=148957&p2=spamassassin/trunk/rules/init.pre&r2=148958
==============================================================================
--- spamassassin/trunk/rules/init.pre (original)
+++ spamassassin/trunk/rules/init.pre Fri Jan 28 16:07:26 2005
@@ -28,6 +28,10 @@
#
loadplugin Mail::SpamAssassin::Plugin::SPF
+# DCC - perform DCC message checks.
+#
+loadplugin Mail::SpamAssassin::Plugin::DCC
+
# Razor2 - perform Razor2 message checks.
#
loadplugin Mail::SpamAssassin::Plugin::Razor2