in lib/Mail/SpamAssassin/Plugin/DCC.pm [972:1157]
sub ask_dcc {
my ($self, $tag, $pms, $fulltext, $envelope) = @_;
my $conf = $pms->{conf};
my $timeout = $conf->{dcc_timeout};
if ($self->is_dccifd_available()) {
my @resp;
my $timer = Mail::SpamAssassin::Timeout->new(
{ secs => $timeout, deadline => $pms->{master_deadline} });
my $err = $timer->run_and_catch(sub {
local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" };
$pms->{dcc_sock} = $self->dccifd_connect($tag);
if (!$pms->{dcc_sock}) {
$self->{dccifd_available} = 0;
# fall back on dccproc if the socket is an orphan from
# a killed dccifd daemon or some other obvious (no timeout) problem
dbg("$tag dccifd failed: trying dccproc as fallback");
return;
}
# send the options and other parameters to the daemon
my $client = $envelope->{ip};
my $clientname = $envelope->{rdns};
if (!defined $client) {
$client = '';
} else {
$client .= ("\r" . $clientname) if defined $clientname;
}
my $helo = $envelope->{helo} || '';
my $opts;
if ($tag eq 'dcc:') {
$opts = $self->{dccifd_lookup_options};
if (defined $pms->{dcc_x_result}) {
# only query if there is an X-DCC header
$opts =~ s/grey-off/grey-off query/;
}
} else {
$opts = $self->{dccifd_report_options};
}
$pms->{dcc_sock}->print($opts) or die "failed write options\n";
$pms->{dcc_sock}->print("$client\n") or die "failed write SMTP client\n";
$pms->{dcc_sock}->print("$helo\n") or die "failed write HELO value\n";
$pms->{dcc_sock}->print("\n") or die "failed write sender\n";
$pms->{dcc_sock}->print("unknown\n\n") or die "failed write 1 recipient\n";
$pms->{dcc_sock}->print($$fulltext) or die "failed write mail message\n";
$pms->{dcc_sock}->shutdown(1) or die "failed socket shutdown: $!";
# don't async report and learn
if ($tag ne 'dcc:') {
@resp = $pms->{dcc_sock}->getlines();
delete $pms->{dcc_sock};
shift @resp; shift @resp; # ignore status/multistatus line
if (!@resp) {
die("no response");
}
} else {
$pms->{dcc_async_start} = time;
}
});
if ($timer->timed_out()) {
delete $pms->{dcc_sock};
dbg("$tag dccifd timed out after $timeout seconds");
return (undef, undef);
} elsif ($err) {
delete $pms->{dcc_sock};
chomp $err;
info("$tag dccifd failed: $err");
return (undef, undef);
}
# report, learn
if ($tag ne 'dcc:') {
my ($raw_x_dcc, $cksums) = $self->parse_dcc_response(\@resp, 'dccifd');
if ($raw_x_dcc) {
dbg("$tag dccifd responded with '$raw_x_dcc'");
return ($raw_x_dcc, $cksums);
} else {
return (undef, undef);
}
}
# async lookup
return ('async', undef) if $pms->{dcc_async_start};
# or falling back to dccproc..
}
if ($self->is_dccproc_available()) {
$pms->enter_helper_run_mode();
my $pid;
my @resp;
my $timer = Mail::SpamAssassin::Timeout->new(
{ secs => $timeout, deadline => $pms->{master_deadline} });
my $err = $timer->run_and_catch(sub {
local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" };
# use a temp file -- open2() is unreliable, buffering-wise, under spamd
my $tmpf = $pms->create_fulltext_tmpfile();
my @opts = split(/\s+/, $conf->{dcc_options} || '');
untaint_var(\@opts);
unshift(@opts, '-w', 'whiteclnt');
my $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 $pms->{dcc_x_result};
} else {
# learn or report spam
unshift(@opts, '-t', 'many');
}
if ($conf->{dcc_home}) {
# set home directory explicitly
unshift(@opts, '-h', $conf->{dcc_home});
}
dbg("$tag opening pipe to " .
join(' ', $conf->{dcc_path}, "-C", "-x", "0", @opts, "<$tmpf"));
$pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*DCC,
$tmpf, 1, $conf->{dcc_path}, "-C", "-x", "0", @opts);
$pid or die "DCC: $!\n";
# read+split avoids a Perl I/O bug (Bug 5985)
my($inbuf, $nread);
my $resp = '';
while ($nread = read(DCC, $inbuf, 8192)) { $resp .= $inbuf }
defined $nread or die "error reading from pipe: $!";
@resp = split(/^/m, $resp, -1);
my $errno = 0;
close DCC or $errno = $!;
proc_status_ok($?,$errno)
or info("$tag [%s] finished: %s", $pid, exit_status_str($?,$errno));
die "failed to read X-DCC header from dccproc\n" if !@resp;
});
if (defined(fileno(*DCC))) { # still open
if ($pid) {
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("$tag [%s] dccproc terminated: %s",
$pid, exit_status_str($?,$errno));
}
$pms->leave_helper_run_mode();
if ($timer->timed_out()) {
dbg("$tag dccproc timed out after $timeout seconds");
return (undef, undef);
} elsif ($err) {
chomp $err;
info("$tag dccproc failed: $err");
return (undef, undef);
}
my ($raw_x_dcc, $cksums) = $self->parse_dcc_response(\@resp, 'dccproc');
if ($raw_x_dcc) {
dbg("$tag dccproc responded with '$raw_x_dcc'");
return ($raw_x_dcc, $cksums);
} else {
info("$tag instead of X-DCC header, dccproc returned '$raw_x_dcc'");
return (undef, undef);
}
}
return (undef, undef);
}