Mail-Milter-Authentication-Extra/lib/Mail/Milter/Authentication/Handler/ClamAV.pm
package Mail::Milter::Authentication::Handler::ClamAV;
use strict;
use warnings;
use base 'Mail::Milter::Authentication::Handler';
our $VERSION = '2.20180611'; # VERSION
# ABSTRACT: Clam AV Scanner for Authentication Milter
use English qw{ -no_match_vars };
use Sys::Syslog qw{:standard :macros};
use ClamAV::Client;
use JSON;
use Mail::AuthenticationResults::Header::Entry;
use Mail::AuthenticationResults::Header::SubEntry;
use Mail::AuthenticationResults::Header::Comment;
use Data::Dumper;
sub default_config {
return {
'ca_name' => '/var/run/clamav/clamd.ctl',
'ca_host' => '127.0.0.1',
'ca_port' => '3310',
'hard_reject' => 1,
};
}
sub grafana_rows {
my ( $self ) = @_;
my @rows;
push @rows, $self->get_json( 'ClamAV_metrics' );
return \@rows;
}
sub register_metrics {
return {
'clamav_total' => 'The number of emails processed for ClamAV',
};
}
sub envfrom_callback {
my ($self, $from) = @_;
$self->{'lines'} = [];
return;
}
sub header_callback {
my ( $self, $header, $value ) = @_;
push @{$self->{'lines'}} ,$header . ': ' . $value . "\r\n";
my $config = $self->handler_config();
return;
}
sub eoh_callback {
my ( $self ) = @_;
push @{$self->{'lines'}} , "\r\n";
return;
}
sub body_callback {
my ( $self, $chunk ) = @_;
push @{$self->{'lines'}} , $chunk;
return;
}
sub eom_callback {
my ($self) = @_;
my $config = $self->handler_config();
my %args;
foreach my $param ( qw{ name host port } ) {
$args{ 'socket_' . $param } = $config->{ 'socket_' . $param } if $config->{ 'socket_' . $param };
}
my $scanner = ClamAV::Client->new( %args );
if ( ! $scanner ) {
$self->log_error( 'ClamAVError: No Scanner' );
my $header = Mail::AuthenticationResults::Header::Entry->new()->set_key( 'x-virus' )->safe_set_value( 'temperror' );
$self->add_auth_header( $header );
$self->metric_count( 'clamav_total', { 'result' => 'noscanner' } );
return;
}
if ( ! $scanner->ping() ) {
$self->log_error( 'ClamAVError: Scanner Ping Failed' );
my $header = Mail::AuthenticationResults::Header::Entry->new()->set_key( 'x-virus' )->safe_set_value( 'temperror' );
$self->add_auth_header( $header );
$self->metric_count( 'clamav_total', { 'result' => 'scannerpingfail' } );
return;
}
my $message = join( q{} , @{$self->{'lines'} } );
my $result = $scanner->scan_scalar( \$message );
if ( $result ) {
$self->dbgout( 'ClamAV: Virus Found', $result, LOG_INFO );
$self->metric_count( 'clamav_total', { 'result' => 'fail' } );
my $header = Mail::AuthenticationResults::Header::Entry->new()->set_key( 'x-virus' )->safe_set_value( 'fail' );
$header->add_child( Mail::AuthenticationResults::Header::Comment->new()->safe_set_value( $result ) );
$self->add_auth_header($header);
if ( $config->{'hard_reject'} ) {
if ( ( ! $self->is_local_ip_address() ) && ( ! $self->is_trusted_ip_address() ) ) {
$self->reject_mail( '550 5.7.0 Virus policy violation' );
$self->dbgout( 'ClamAVReject', "Policy reject", LOG_INFO );
}
}
}
else {
$self->metric_count( 'clamav_total', { 'result' => 'pass' } );
my $header = Mail::AuthenticationResults::Header::Entry->new()->set_key( 'x-virus' )->safe_set_value( 'pass' );
$self->add_auth_header($header);
}
return;
}
sub close_callback {
my ( $self ) = @_;
delete $self->{'lines'};
return;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Mail::Milter::Authentication::Handler::ClamAV - Clam AV Scanner for Authentication Milter
=head1 VERSION
version 2.20180611
=head1 DESCRIPTION
Virus check email for using clamav.
=head1 CONFIGURATION
"ClamAV" : {
"hard_reject" : "1",
"ca_name" : "/var/run/clamav/clamd.ctl",
"ca_host" : "127.0.0.1",
"ca_port" : "3310"
},
=head2 CONFIG
Add a block to the handlers section of your config as follows.
"ClamAV" : {
"hard_reject" : "1",
"ca_name" : "/var/run/clamav/clamd.ctl",
"ca_host" : "127.0.0.1",
"ca_port" : "3310"
},
=head1 AUTHOR
Marc Bradshaw <marc@marcbradshaw.net>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2018 by Marc Bradshaw.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut