Qpsmtpd-Plugin-Quarantine

 view release on metacpan or  search on metacpan

lib/Qpsmtpd/Plugin/Quarantine/Spam.pm  view on Meta::CPAN

	my $x = read_file($file);

	my %results;

	TEST:
	for(;;) {
		if (($defaults{clamd} || $defaults{clamav}) && 
			($headers->get('Content-Type') =~ /$defaults{virus_content}/ 
			|| $headers->get('Content-Disposition') =~ /$defaults{virus_content}/ 
			|| ($headers->get('Content-Type') =~ /multipart/
				&& $transaction->body_as_string() =~ /^Content-Type: $defaults{virus_content}/m)))
		{
			last if 
				clamav($qp, $transaction, \%results, ($defaults{clamd} || $defaults{clamav}), 'stream');
			unless (exists $results{clamav}) {
				last if
					clamav($qp, $transaction, \%results, ($defaults{clamd} || $defaults{clamav}), $file);
			}
		}

		for my $spamd3 (@spamd3) {
			last TEST if 
				spamc($qp, $transaction, \%results, $spamd3, 'SPAMASSASSIN3');
			last if $results{spamc};
		}

		last if $results{filtered};

		if ($defaults{clamd}) {
			unless (exists $results{clamav}) {
				last if
					clamav($qp, $transaction, \%results, $defaults{clamd}, 'stream');
			}
		}

		if ($defaults{clamav}) {
			unless (exists $results{clamav}) {
				last if
					clamav($qp, $transaction, \%results, $defaults{clamav}, $file);
			}
		}
		
		last;
	}
	$qp->log(LOGDEBUG, "Filtered? $results{filterd}");
	$qp->log(LOGDEBUG, "Details: $results{hlines}");
	return $results{filtered};
}

sub clamav
{
	my ($qp, $transaction, $r, $command, $input) = @_;
	my $results;
	my $file = $transaction->body_filename();
	eval {
		local($SIG{__DIE__}) = 'DEFAULT';
		local($SIG{ALRM}) = sub { 
			$qp->log(LOGWARN, "ClamAV timeout");
			die "timeout\n";
		};
		alarm($defaults{subcommand_timeout}) if $defaults{subcommand_timeout};
		$results = `$command $file`;
	};
	unless ($results =~ /-- SCAN SUMMARY --/ && $results =~ /^\Q$input\E: (OK|.*FOUND)\n/) {
		$qp->log(LOGINFO, "ClamAV failed: $results");
		$r->{clamav} = undef;
		return 0;
	}
	my $return = 0;
	my $status = $1;
	if ($status =~ /\n\n/) {
		$qp->log(LOGINFO, "ClamAV Failed: $results");
		$r->{clamav} = undef;
		return 0;
	} elsif ($status eq "OK") {
		$status = "X-ClamAV-Status: No\n";
	} else {
		$status = "X-ClamAV-Status: Yes, $status\n";
		$r->{filtered} .= " CLAMAV";
		$return = 1;
	}
	$r->{clamav} = $status;
	$r->{hlines} .= $status;
	return $return;
}

sub results_in_headers
{
	my ($qp, $transaction, $r, $command, $lookfor) = @_;
	$qp->log(LOGDEBUG, "running $command...");
	local($/) = "\n\n";
	my $file = $transaction->body_filename();
	unless (open(RESULTS, "$command < $file|")) {
		$qp->log(LOGWARN, "Could not open $command: $!");
		return '';
	}
	my $h = <RESULTS>;
	$/ = "\n";
	chomp($h);
	close(RESULTS);

	my $fileheader = $transaction->header()->as_string();
	my $fileheaderlen = length($fileheader);

	if (substr($h, 0, $fileheaderlen) eq $fileheader) {
		substr($h, 0, $fileheaderlen) = '';
	} elsif ($h =~ /^(From .*\n)/g) {
		# weird, with qpsmtpd we don't have a "From " line
	} elsif ($h =~ /^[A-Z][-\w]*:/) {
		# that's good
	} else {
		$qp->log(LOGWARN, "Could not parse output from $command");
		return '';
	}
	my $found = '';
	my %found;
	pos($h) = 0;
	while ($h =~ /$headerrx/gco) {
		my ($k, $v) = ($1, $2);
		next unless $lookfor->{$k};
		$found{$k} = $v;
		$found .= "$k:$v\n";
	}
	if (pos($h) != length($h)) {
		$qp->log(LOGWARN, "Unparsed header from $command");
		return '';
	}
	return ($found, %found);
}

sub spamc 
{
	my ($qp, $transaction, $r, $command, $tag) = @_;
	my $file = $transaction->body_filename();
	$qp->log(LOGDEBUG, "running $command...");
	my $results;
	eval {
		local($SIG{__DIE__}) = 'DEFAULT';
		local($SIG{ALRM}) = sub { 
			$qp->log(LOGWARN, "TIMEOUT $command $file");
			die "timeout\n";
		};
		alarm($defaults{subcommand_timeout}) if $defaults{subcommand_timeout};
		$results = `$command $file`;
	};
	$results =~ m!^(-?\d+\.\d+)/(\d+\.\d+)\n!;
	my ($score, $thresh) = ($1 || '', $2 || '');
	unless ($thresh && $thresh > 0 && ($results =~ s/.*^[- ]{25,300}\n//sm)) {
		$results = substr($results, 0, 30);
		$results =~ s/\n/\\n/g;
		$qp->log(LOGDEBUG, "'$command $file' failed $thresh: $results");
		return '';
	}
	my @tests;
	my $report = '';
	$thresh = 5.0;
	while ($results =~ /\G {0,5}(-?\d+(?:\.\d+)?) (\S+)\s+(\S(?:.+(?=\n)|\n(?= {6}))+)\n/g) {
		my ($pts, $rule, $desc) = ($1, $2, $3);
		$desc =~ s/\n {5,60}(\S)/\n\t*      $1/g;
		push(@tests, $rule);
		$report .= sprintf("\t* % 4s %s %s\n", $pts, $rule, $desc);
	}
	my $xss;
	my $tests = join(',', @tests);
	if ($score >= $thresh) {
		$xss = "X-Spam-Status: Yes, hits=$score required=$thresh tests=$tests\n";
		$r->{filtered} .= " $tag";
		$report = "X-Spam-Report:\n$report";
	} else {
		$xss = "X-Spam-Status: No, hits=$score required=$thresh tests=$tests\n";
		$report = "";
	}
	1 while $xss =~ s/^(.{60}.*?,)(?=.)/$1\n\t/m;
	my $rout = "$xss$report";
	if ($rout =~ /\n\n/) {
		$qp->log(LOGWARN, "$command $file: failed doubleNL $results");
		return '';
	}
	$r->{spamc} = $rout;
	$r->{hlines} .= $rout;
	return $report;
}

1;



( run in 1.408 second using v1.01-cache-2.11-cpan-39bf76dae61 )