clamdscan: add support for remote TCP/IP clamd
previous version only worked when clamd was running on the same machine and had access to the spool file. This version also works with a remote clamd.
This commit is contained in:
parent
81bf413d30
commit
3353578d8b
@ -10,6 +10,8 @@ A qpsmtpd plugin for virus scanning using the ClamAV scan daemon, clamd.
|
|||||||
|
|
||||||
=head1 RESTRICTIONS
|
=head1 RESTRICTIONS
|
||||||
|
|
||||||
|
If connecting to clamd via TCP/IP host:port, then ignore this restriction.
|
||||||
|
|
||||||
The ClamAV scan daemon, clamd, must have at least execute access to the qpsmtpd
|
The ClamAV scan daemon, clamd, must have at least execute access to the qpsmtpd
|
||||||
spool directory in order to sucessfully scan the messages. You can ensure this
|
spool directory in order to sucessfully scan the messages. You can ensure this
|
||||||
by running clamd as the same user as qpsmtpd does, or by doing the following:
|
by running clamd as the same user as qpsmtpd does, or by doing the following:
|
||||||
@ -47,19 +49,26 @@ You must have the ClamAV::Client module installed to use the plugin.
|
|||||||
|
|
||||||
=item B<clamd_socket>
|
=item B<clamd_socket>
|
||||||
|
|
||||||
Full path to the clamd socket (the recommended mode), if different from the
|
Full path to the clamd socket, if different from the ClamAV::Client defaults.
|
||||||
ClamAV::Client defaults.
|
|
||||||
|
=item B<clamd_host>
|
||||||
|
|
||||||
|
IP address where clamd is listening.
|
||||||
|
|
||||||
|
Default: localhost
|
||||||
|
|
||||||
=item B<clamd_port>
|
=item B<clamd_port>
|
||||||
|
|
||||||
If present, must be the TCP port where the clamd service is running,
|
The TCP port where the clamd service is running, typically 3310.
|
||||||
typically 3310; default disabled. If present, overrides the clamd_socket.
|
|
||||||
|
Default: disabled. When present, overrides clamd_socket.
|
||||||
|
|
||||||
=item B<deny_viruses>
|
=item B<deny_viruses>
|
||||||
|
|
||||||
Whether the scanner will automatically delete messages which have viruses.
|
Whether the scanner will automatically delete messages which have viruses.
|
||||||
Takes either 'yes' or 'no' (defaults to 'yes'). If set to 'no' it will add
|
Takes either 'yes' or 'no'. If set to 'no', adds a header with the virus name.
|
||||||
a header to the message with the virus results.
|
|
||||||
|
Default: yes
|
||||||
|
|
||||||
=item B<defer_on_error>
|
=item B<defer_on_error>
|
||||||
|
|
||||||
@ -71,7 +80,9 @@ backlog or be lost if the condition persists.
|
|||||||
|
|
||||||
=item B<max_size>
|
=item B<max_size>
|
||||||
|
|
||||||
The maximum size, in kilobytes, of messages to scan; defaults to 128k.
|
The maximum size, in kilobytes, of messages to scan.
|
||||||
|
|
||||||
|
Default: 1024 (1 MB)
|
||||||
|
|
||||||
=item B<scan_all>
|
=item B<scan_all>
|
||||||
|
|
||||||
@ -94,6 +105,7 @@ adjusted for ClamAV::Client by Devin Carraway <qpsmtpd/@/devin.com>.
|
|||||||
|
|
||||||
Copyright (c) 2005 John Peacock,
|
Copyright (c) 2005 John Peacock,
|
||||||
Copyright (c) 2007 Devin Carraway
|
Copyright (c) 2007 Devin Carraway
|
||||||
|
Copyright (c) 2013 Matt Simerson
|
||||||
|
|
||||||
Based heavily on the clamav plugin
|
Based heavily on the clamav plugin
|
||||||
|
|
||||||
@ -106,10 +118,13 @@ use strict;
|
|||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
#use ClamAV::Client; # eval'ed in $self->register
|
#use ClamAV::Client; # eval'ed in $self->register
|
||||||
|
use Socket qw(:DEFAULT :crlf);
|
||||||
|
|
||||||
use Qpsmtpd::Constants;
|
use Qpsmtpd::Constants;
|
||||||
|
|
||||||
sub register {
|
sub register {
|
||||||
my ($self, $qp) = shift, shift;
|
my $self = shift;
|
||||||
|
my $qp = shift;
|
||||||
|
|
||||||
$self->log(LOGERROR, "Bad parameters for the clamdscan plugin") if @_ % 2;
|
$self->log(LOGERROR, "Bad parameters for the clamdscan plugin") if @_ % 2;
|
||||||
$self->{'_args'} = {@_};
|
$self->{'_args'} = {@_};
|
||||||
@ -138,7 +153,6 @@ sub register {
|
|||||||
sub data_post_handler {
|
sub data_post_handler {
|
||||||
my ($self, $transaction) = @_;
|
my ($self, $transaction) = @_;
|
||||||
|
|
||||||
my $filename = $self->get_filename($transaction) or return DECLINED;
|
|
||||||
|
|
||||||
if ($self->connection->notes('naughty')) {
|
if ($self->connection->notes('naughty')) {
|
||||||
$self->log(LOGINFO, "skip, naughty");
|
$self->log(LOGINFO, "skip, naughty");
|
||||||
@ -147,8 +161,6 @@ sub data_post_handler {
|
|||||||
return (DECLINED) if $self->is_too_big($transaction);
|
return (DECLINED) if $self->is_too_big($transaction);
|
||||||
return (DECLINED) if $self->is_not_multipart($transaction);
|
return (DECLINED) if $self->is_not_multipart($transaction);
|
||||||
|
|
||||||
$self->set_permission($filename) or return DECLINED;
|
|
||||||
|
|
||||||
my $clamd = $self->get_clamd()
|
my $clamd = $self->get_clamd()
|
||||||
or return $self->err_and_return("Cannot instantiate ClamAV::Client");
|
or return $self->err_and_return("Cannot instantiate ClamAV::Client");
|
||||||
|
|
||||||
@ -159,7 +171,18 @@ sub data_post_handler {
|
|||||||
my ($version) = split(/\//, $clamd->version);
|
my ($version) = split(/\//, $clamd->version);
|
||||||
$version ||= 'ClamAV';
|
$version ||= 'ClamAV';
|
||||||
|
|
||||||
my ($path, $found) = eval { $clamd->scan_path($filename) };
|
my ($path, $found);
|
||||||
|
if ( $self->{_args}{clamd_port} ) {
|
||||||
|
my $message = $self->assemble_message($transaction);
|
||||||
|
$found = eval { $clamd->scan_scalar(\$message) }; # pass scalar ref
|
||||||
|
# $found = eval { $clamd->scan_stream() }; # pass IO handle
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my $filename = $self->get_filename($transaction) or return DECLINED;
|
||||||
|
$self->set_permission($filename) or return DECLINED;
|
||||||
|
($path, $found) = eval { $clamd->scan_path($filename) };
|
||||||
|
};
|
||||||
|
|
||||||
if ($@) {
|
if ($@) {
|
||||||
return $self->err_and_return("Error scanning mail: $@");
|
return $self->err_and_return("Error scanning mail: $@");
|
||||||
}
|
}
|
||||||
@ -186,6 +209,15 @@ sub data_post_handler {
|
|||||||
return (DECLINED);
|
return (DECLINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub assemble_message {
|
||||||
|
my ($self, $transaction) = @_;
|
||||||
|
$transaction->body_resetpos;
|
||||||
|
my $message = $transaction->header->as_string . "\n\n";
|
||||||
|
while (my $line = $transaction->body_getline) { $message .= $line; }
|
||||||
|
$message = join(CRLF, split /\n/, $message);
|
||||||
|
return $message . CRLF;
|
||||||
|
}
|
||||||
|
|
||||||
sub err_and_return {
|
sub err_and_return {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $message = shift;
|
my $message = shift;
|
||||||
|
Loading…
Reference in New Issue
Block a user