Last active
December 27, 2015 06:09
-
-
Save dex4er/7279409 to your computer and use it in GitHub Desktop.
exifilter - Pass-through filter which scans mail for viruses
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/perl | |
| ## exifilter | |
| ## | |
| ## (c) 2003-2004 Piotr Roszatycki <dexter@debian.org>, GPL | |
| ## | |
| ## $Id: exifilter.pl 161 2004-09-01 11:04:11Z dexter $ | |
| =head1 NAME | |
| exifilter - Pass-through filter which scans mail for viruses. | |
| =head1 SYNOPSIS | |
| B<exifilter> S<B<-h>> | |
| B<exifilter> | |
| S<[B<--no-cleanup>]> | |
| S<[B<--debug-stderr>]> | |
| S<[B<--debug-logfile>]> | |
| S<[B<--test>]> | |
| S<[B<--tmpdir> I<directory>]> | |
| S<[B<--logfile> I<path>]> | |
| S<[B<--scanner> B<none>|B<clamav>|B<clamavd>]> | |
| S<[B<--timeout-scan> I<seconds>]> | |
| S<[B<--pipe> I<command>]> | |
| I<primary_hostname> | |
| I<message_id> | |
| =cut | |
| use 5.006; | |
| use strict; | |
| ############################################################################## | |
| ## Default values for constant variables | |
| ## | |
| ## Program name | |
| my $NAME = "exifilter"; | |
| ## Program version | |
| my $VERSION = 0.2; | |
| ## Path for temporary files | |
| my $TMPDIR = "/tmp"; | |
| ## Path for log files | |
| my $LOGFILE = "/var/log/exifilter/%s.log"; | |
| ## Default scanner | |
| my $SCANNER = "clamavd"; | |
| ## Default timeout time for scanner | |
| my $TIMEOUT_SCAN = 180; | |
| ## RLIMIT_* variables, see BSD::Resource(3) | |
| ## undef means default value | |
| ## | |
| ## file size (bytes), causes kill with SIGXFSZ | |
| my $RLIMIT_FSIZE = 100*1024*1024; | |
| ## number of open files, causes i/o function error EMFILE | |
| my $RLIMIT_OPEN_MAX = 20; | |
| ## virtual address space (bytes) | |
| my $RLIMIT_AS = 100*1024*1024; | |
| ## CPU time (seconds), causes kill with SIGXCPU, after 10 sec. kill with SIGKILL | |
| my $RLIMIT_CPU = 60; | |
| ############################################################################## | |
| ## Default file names | |
| ## | |
| ## Time stamp file | |
| my $stampfile = "timestamp"; | |
| ## Original mail from STDIN | |
| my $mailfile = "mail"; | |
| ## Definitions for antivirus scanners | |
| ## | |
| my %scanners = ( | |
| 'testok' => { | |
| cmd => | |
| 'ls -l <TMPDIR>/<FILE>', | |
| found_regexp => '^-', | |
| status => { | |
| 0 => "OK", | |
| 1 => "Unknown error" } }, | |
| 'testvirus' => { | |
| cmd => | |
| 'ls -l <TMPDIR>/<FILE>', | |
| found_regexp => '^-', | |
| status => { | |
| 0 => "VIRUS", | |
| 1 => "Unknown error" } }, | |
| 'clamav' => { | |
| cmd => | |
| 'clamscan --stdout --recursive --infected --no-summary --mbox ' | |
| . '--max-files=500 --max-space=20m --max-recursion=5 ' | |
| . '<FILE>', | |
| found_regexp => 'mail: (.*\sFOUND)$', | |
| status => { | |
| 0 => "OK", | |
| 1 => "VIRUS", | |
| 40 => "Unknown option passed.", | |
| 50 => "The database can't be initialized.", | |
| 52 => "Not supported file type.", | |
| 53 => "Can't open directory.", | |
| 54 => "Can't open file. (ofm)", | |
| 55 => "Error reading file. (ofm)", | |
| 56 => "Can't stat input file / directory.", | |
| 57 => "Can't get absolute pathname of current working directory.", | |
| 58 => "I/O error, check your filesystem.", | |
| 59 => "Can't get information about current user from /etc/passwd.", | |
| 60 => "Can't get information about user 'clamav' (default name) from /etc/passwd.", | |
| 61 => "Can't fork.", | |
| 63 => "Can't create temporary files/directories (check permissions).", | |
| 64 => "Can't write to temporary directory, please specify another one.", | |
| 70 => "Can't allocate and clear memory (calloc).", | |
| 71 => "Can't allocate memory (malloc)." } }, | |
| 'clamavd' => { | |
| cmd => | |
| 'clamdscan --stdout --disable-summary <FILE>', | |
| found_regexp => 'mail: (.*\sFOUND)$', | |
| status => { | |
| 0 => "OK", | |
| 1 => "VIRUS", | |
| 2 => "An error occured." } }, | |
| ); | |
| ## Signals to handle | |
| ## | |
| my @signals = qw( HUP INT QUIT TERM SEGV PIPE XCPU XFSZ ); | |
| ## Explains of system errors | |
| ## | |
| my %explains = ( | |
| '???' => "The message couldn't be scanned. The scanning process stopped\n" | |
| ."unexpectedly. It could be a system error or message contained\n" | |
| . "too big attachments or it was causedby DoS attack on mail server.", | |
| '1' => "The message or its attachment contained virus code.", | |
| '2' => "The message couldn't be scanned. It could be a system error or\n" | |
| . "message contained too big attachments or it was causedby DoS attack\n" | |
| . "on mail server.", | |
| 'SIGXCPU' => "Scanning process was too exhausting.", | |
| 'SIGXFSZ' => "The message contained too big attachments.", | |
| ); | |
| ############################################################################## | |
| ## Internal handlers and global variables | |
| ## | |
| ## Getopt::Long handler | |
| my %opt = ( | |
| 'tmpdir' => $TMPDIR, | |
| 'logfile' => $LOGFILE, | |
| 'scanner' => $SCANNER, | |
| 'timeout-scan' => $TIMEOUT_SCAN, | |
| ); | |
| ## Temporary directory | |
| my $tmpdir; | |
| ## PID for fork function | |
| my $pid; | |
| ## Flag if mail is already dumped to file | |
| my $is_mail_dumped; | |
| ## Primary hostname used in headers | |
| my $primary_hostname; | |
| ## Message id used in logging and archiving | |
| my $message_id; | |
| ############################################################################## | |
| ## date_rfc822() | |
| ## | |
| ## Return date in rfc822 format. | |
| ## Based on 822-date from dpkg package. | |
| ## | |
| sub date_rfc822() { | |
| my $curtime = time; | |
| my @localtm = localtime($curtime); | |
| my $localtms = localtime($curtime); | |
| my @gmttm = gmtime($curtime); | |
| my $gmttms = gmtime($curtime); | |
| if ($localtm[0] != $gmttm[0]) { | |
| die(sprintf("local timezone differs from GMT by a non-minute interval\n" | |
| . "local time: %s\n" | |
| . "GMT time: %s\n", $localtms, $gmttms)); | |
| } | |
| my $localmin = $localtm[1] + $localtm[2] * 60; | |
| my $gmtmin = $gmttm[1] + $gmttm[2] * 60; | |
| if ((($gmttm[6] + 1) % 7) == $localtm[6]) { | |
| $localmin += 1440; | |
| } elsif ((($gmttm[6] - 1) % 7) == $localtm[6]) { | |
| $localmin -= 1440; | |
| } elsif ($gmttm[6] == $localtm[6]) { | |
| 1; | |
| } else { | |
| die "822-date: local time offset greater than or equal to 24 hours\n"; | |
| } | |
| my $offset = $localmin - $gmtmin; | |
| my $offhour = $offset / 60; | |
| my $offmin = abs($offset % 60); | |
| if (abs($offhour) >= 24) { | |
| die "822-date: local time offset greater than or equal to 24 hours\n"; | |
| } | |
| return sprintf( | |
| "%s, %2d %s %d %02d:%02d:%02d %s%02d%02d", | |
| qw(Sun Mon Tue Wed Thu Fri Sat)[$localtm[6]], # day of week | |
| $localtm[3], # day of month | |
| qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$localtm[4]], # month | |
| $localtm[5]+1900, # year | |
| $localtm[2], # hour | |
| $localtm[1], # minute | |
| $localtm[0], # sec | |
| ($offset >= 0) ? '+' : '-',# TZ offset direction | |
| abs ($offhour), # TZ offset hour | |
| $offmin, # TZ offset minute | |
| ); | |
| } | |
| ## debug($msg, ...) | |
| ## | |
| ## Dumps message if debug mode is turned on. | |
| ## | |
| sub debug(@) { | |
| if (${opt{'debug-logfile'}}) { | |
| open LOG, sprintf ">>" . $opt{'logfile'}, "debug" and | |
| print LOG scalar localtime() . " " . $NAME . "[" . $$ . "]: " . join('',@_) . "\n"; | |
| close LOG; | |
| } | |
| if (${opt{'debug-stderr'}}) { | |
| print STDERR scalar localtime() . " " . $NAME . "[" . $$ . "]: " . join('',@_) . "\n"; | |
| } | |
| } | |
| ## error($msg, ...) | |
| ## | |
| ## Pass through and let to die. | |
| ## | |
| sub error(@) { | |
| open LOG, sprintf ">>" . $opt{'logfile'}, "error" and | |
| print LOG scalar localtime() . " " . $NAME . "[" . $$ . "]: " . join('',@_) . "\n"; | |
| close LOG; | |
| if (${opt{'debug-stderr'}}) { | |
| print STDERR scalar localtime() . " " . $NAME . "[" . $$ . "]: " . join('',@_) . "\n"; | |
| } | |
| if ($opt{pipe}) { | |
| debug("open pipe $opt{pipe}"); | |
| open PIPE, "|$opt{pipe}" | |
| or die "Can not open pipe `$opt{pipe}': $!\n"; | |
| open OUT, ">&PIPE"; | |
| } else { | |
| open OUT, ">&STDOUT"; | |
| } | |
| if (not $is_mail_dumped) { | |
| while ($_ = <STDIN>) { | |
| print OUT or die "Can not write to output: $!\n"; | |
| } | |
| } else { | |
| open MAIL, "$tmpdir/$mailfile" or die "Could not open file $tmpdir/$mailfile: $!\n"; | |
| while ($_ = <MAIL>) { | |
| print OUT or die "Can not write to output: $!\n"; | |
| } | |
| close MAIL; | |
| } | |
| if ($opt{pipe}) { | |
| debug("close pipe $opt{pipe}"); | |
| close PIPE; | |
| } | |
| cleanup(); | |
| exit 0; | |
| } | |
| ## usage() | |
| ## | |
| ## Prints usage message. | |
| ## | |
| sub usage() { | |
| eval 'use Pod::Usage;'; | |
| pod2usage(2); | |
| } | |
| ## help() | |
| ## | |
| ## Prints help message. | |
| ## | |
| sub help() { | |
| eval 'use Pod::Usage;'; | |
| pod2usage(-verbose=>1, -message=>"$NAME $VERSION\n"); | |
| } | |
| ## $path = replace_XX($template) | |
| ## | |
| ## Replace "X" characters with randomize chars. Used by mktempdir() function. | |
| ## | |
| sub replace_XX($) { | |
| my ($path) = @_; | |
| my @CHARS = (qw/ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z | |
| a b c d e f g h i j k l m n o p q r s t u v w x y z | |
| 0 1 2 3 4 5 6 7 8 9 _ | |
| /); | |
| $path =~ s/X(?=X*\z)/$CHARS[ int( rand( $#CHARS ) ) ]/ge; | |
| return $path; | |
| } | |
| ## $path = mktempdir($template) | |
| ## | |
| ## Create temporary directory. | |
| ## | |
| sub mktempdir($) { | |
| my ($template) = @_; | |
| my $MAX_TRIES = 50; | |
| my $OPENFLAGS = 194; # O_CREAT | O_EXCL | O_RDWR | |
| for (my $i = 0; $i < $MAX_TRIES; $i++) { | |
| my $fh; | |
| my $path = replace_XX($template); | |
| ## Store callers umask | |
| my $umask = umask(); | |
| ## Set a known umask | |
| umask(026); | |
| # Open the temp directory | |
| if (mkdir($path, 0750)) { | |
| # created okay | |
| # Reset umask | |
| umask($umask) if defined $umask; | |
| return $path; | |
| } else { | |
| # Reset umask | |
| umask($umask) if defined $umask; | |
| # Abort with error if the reason for failure was anything | |
| # except EEXIST | |
| unless ($!{EEXIST}) { | |
| error("Could not create directory $path: $!"); | |
| } | |
| # Loop round for another try | |
| } | |
| } | |
| } | |
| ## $status = rmsubtree($path, $dh) | |
| ## | |
| ## Remove directory tree with named directory handler. | |
| ## Used by rmtree() function. | |
| ## | |
| sub rmsubtree($$) { | |
| my ($path, $dh) = @_; | |
| $dh++; | |
| no strict 'refs'; | |
| opendir $dh, $path or return 0; | |
| while ($_ = readdir $dh) { | |
| if ($_ ne "." and $_ ne "..") { | |
| if (-d "$path/$_") { | |
| chdir $_; | |
| rmsubtree("$path/$_", $dh) or return 0; | |
| } else { | |
| unlink "$path/$_" or return 0; | |
| } | |
| } | |
| } | |
| closedir $dh; | |
| rmdir $path or return 0; | |
| return 1; | |
| } | |
| ## $status = rmtree($path) | |
| ## | |
| ## Remove directory tree. | |
| ## | |
| sub rmtree($) { | |
| my ($path) = @_; | |
| return rmsubtree($path, "DIR000"); | |
| } | |
| ## cleanup($path) | |
| ## | |
| ## Cleanup before die. | |
| ## | |
| sub cleanup() { | |
| return if $opt{'no-cleanup'}; | |
| debug("rmtree $tmpdir"); | |
| rmtree($tmpdir) if $tmpdir; | |
| } | |
| ## die_main($msg, ...) | |
| ## | |
| ## Dump the message and really die. | |
| ## | |
| sub die_main(@) { | |
| cleanup(); | |
| debug(@_); | |
| die @_; | |
| } | |
| ## signal_main($sig) | |
| ## | |
| ## Handler for signals which cleans up temporary directory | |
| ## | |
| sub signal_main($) { | |
| my ($sig) = @_; | |
| die "Caught a SIG$sig -- shutting down\n"; | |
| } | |
| ## scan() | |
| ## | |
| ## Spawn scanner process | |
| ## | |
| sub scan() { | |
| return 1 if $opt{scanner} eq "none"; | |
| ## die handler | |
| $SIG{__DIE__} = sub { die @_; }; | |
| my $scanpid; | |
| use Data::Dumper; | |
| debug $opt{'scanner'}; | |
| my $cmd = $scanners{$opt{'scanner'}}->{cmd}; | |
| $cmd =~ s|<FILE>|$mailfile|g; | |
| $cmd =~ s|<DIRECTORY>|.|g; | |
| $cmd =~ s|<TMPDIR>|$tmpdir|g; | |
| chdir $tmpdir or die "Can not chdir to $tmpdir: $!\n"; | |
| debug("running $cmd"); | |
| ## Fork for exec | |
| if (! defined($scanpid = fork)) { | |
| error("Could not fork: $!"); | |
| } elsif ($scanpid == 0) { | |
| ## child | |
| exec $cmd or die "Can not run command `$cmd': $!\n"; | |
| } | |
| ## parent | |
| eval { | |
| local $SIG{ALRM} = sub { die "timeout-scan\n"; }; | |
| alarm $opt{'timeout-scan'}; | |
| wait; | |
| alarm 0; | |
| }; | |
| if ($@ eq "timeout-scan\n") { | |
| ## timeouted | |
| kill 9, $scanpid; | |
| die "Scanner command was timeouted\n"; | |
| } | |
| ## Check status | |
| $_ = $? & 0xffff; | |
| if ($_ & 0x00ff) { | |
| die "Can not run command `$cmd': $!\n"; | |
| } else { | |
| $_ >>= 8; | |
| my $status = (defined($scanners{$opt{scanner}}->{status}->{$_}) | |
| ? $scanners{$opt{scanner}}->{status}->{$_} | |
| : "Unknown"); | |
| if ($status eq "OK") { | |
| return 1; | |
| } elsif ($status eq "VIRUS") { | |
| return 0; | |
| } else { | |
| die sprintf "Error on command `$cmd': $_ (%s)\n", $status; | |
| } | |
| } | |
| } | |
| ## output(@msg) | |
| ## | |
| ## Output the mail with additional headers. | |
| ## | |
| sub output(@) { | |
| my @msg = @_; | |
| if ($opt{pipe}) { | |
| &debug("open pipe $opt{pipe}"); | |
| open PIPE, "|$opt{pipe}" | |
| or die "Can not open pipe `$opt{pipe}': $!\n"; | |
| open OUT, ">&PIPE"; | |
| } else { | |
| open OUT, ">&STDOUT"; | |
| } | |
| open MAIL, "$tmpdir/$mailfile" or die "Could not open file $tmpdir/$mailfile: $!\n"; | |
| while ($_ = <MAIL>) { | |
| last if $_ =~ /^$/; | |
| next if $_ =~ /^X-Exifilter[:-]/; | |
| print OUT or die "Can not write to output: $!\n"; | |
| } | |
| print OUT "X-Exifilter: $NAME $VERSION on $primary_hostname\n" or die "Can not write to output: $!\n"; | |
| if (@msg) { | |
| print OUT "X-Exifilter-Virus: YES\n" or die "Can not write to output: $!\n"; | |
| } | |
| my $regexp = $scanners{$opt{scanner}}{found_regexp}; | |
| foreach (@msg) { | |
| /$regexp/ or next; | |
| $_ = $1 if $1; | |
| print OUT "X-Exifilter-Detail: $_\n" or die "Can not write to output: $!\n"; | |
| } | |
| print OUT or die "Can not write to output: $!\n"; | |
| while (<MAIL>) { | |
| print OUT or die "Can not write to output: $!\n"; | |
| } | |
| if ($opt{pipe}) { | |
| &debug("close pipe $opt{pipe}"); | |
| close PIPE; | |
| } | |
| close MAIL; | |
| } | |
| ############################################################################## | |
| ## Main subroutine | |
| ## | |
| ## die handler | |
| $SIG{__DIE__} = \&die_main; | |
| ## clean exit for signals | |
| foreach my $sig (@signals) { | |
| $SIG{$sig} = \&signal_main; | |
| } | |
| ## Parse command line arguments | |
| while (@ARGV) { | |
| my $arg = $ARGV[0]; | |
| if ($arg =~ /^--(.*)$/) { | |
| my $opt = $1; | |
| if ($opt eq 'test') { | |
| $opt{'no-cleanup'} = 1; | |
| $opt{'debug-stderr'} = 1; | |
| } elsif ($opt =~ /^(tmpdir|scanner|timeout-scan|pipe)$/) { | |
| usage() unless @ARGV; | |
| shift; | |
| $arg = $ARGV[0] | |
| } elsif ($opt =~ /^(no-cleanup|debug-stderr|debug-logfile)$/) { | |
| $arg = 1; | |
| } elsif ($opt eq 'help') { | |
| help(); | |
| } else { | |
| usage(); | |
| } | |
| $opt{$opt} = $arg; | |
| } else { | |
| help() if $arg eq '-h'; | |
| last; | |
| } | |
| shift @ARGV; | |
| } | |
| usage() if @ARGV > 2; | |
| if (@ARGV < 1) { | |
| if ($opt{'test'}) { | |
| $primary_hostname = '$primary_hostname'; | |
| } else { | |
| eval 'use Sys::Hostname;'; | |
| $primary_hostname = hostname(); | |
| } | |
| } else { | |
| $primary_hostname = $ARGV[0]; | |
| } | |
| if (@ARGV < 2) { | |
| if ($opt{'test'}) { | |
| $message_id = '$message_id'; | |
| } else { | |
| $message_id = sprintf "%10d-%05d", time, $$; | |
| } | |
| } else { | |
| $message_id = $ARGV[1]; | |
| } | |
| ## Read permission for group. Scanner daemon should read the file. | |
| umask(0027); | |
| ## Create tmpdir | |
| $tmpdir = mktempdir("$opt{tmpdir}/${NAME}XXXXXXXXX"); | |
| debug("mktempdir $tmpdir"); | |
| ## Test the writting to filesystem | |
| open STAMP, ">$tmpdir/$stampfile" or error("Could not create file $tmpdir/$stampfile: $!"); | |
| print STAMP scalar localtime or error("Could not write file $tmpdir/$stampfile: $!\n"); | |
| close STAMP or error("Could not close file $tmpdir/$stampfile: $!"); | |
| ## Dump the mail and die if it is not possible | |
| open MAIL, ">$tmpdir/$mailfile" or error("Could not create file $tmpdir/$mailfile: $!"); | |
| $is_mail_dumped = 1; | |
| while (<STDIN>) { | |
| print MAIL or die "Could not write file $tmpdir/$mailfile: $!\n"; | |
| } | |
| close MAIL or die "Could not close file $tmpdir/$mailfile: $!\n"; | |
| ## Fork with unnamed pipe | |
| pipe(PARENT_RDR, CHILD_WTR); | |
| if (! defined($pid = fork)) { | |
| error("Could not fork: $!"); | |
| } elsif ($pid == 0) { | |
| ## child | |
| close PARENT_RDR; | |
| open STDOUT, ">&CHILD_WTR" or die "Can not redirect stdout: $!\n"; | |
| open STDERR, ">&CHILD_WTR" or die "Can not redirect stderr: $!\n"; | |
| eval { | |
| $_ = scan(); | |
| }; | |
| ## 0 = ok, 1 = virus, 2 = error | |
| if ($@ || ($_ > 1 && $_ < 0)) { | |
| debug($@); | |
| debug("exit 2"); | |
| exit 2; | |
| } elsif ($_ == 0) { | |
| debug("exit 1"); | |
| exit 1; | |
| } else { | |
| debug("exit 0"); | |
| exit 0; | |
| } | |
| } | |
| ## parent | |
| my @lines; | |
| close CHILD_WTR; | |
| while ($_ = <PARENT_RDR>) { | |
| chomp; | |
| push @lines, $_; | |
| } | |
| close PARENT_RDR; | |
| wait; | |
| if ($?>>8 == 1) { | |
| debug("Virus found in message $message_id"); | |
| output(@lines); | |
| } elsif ($?>>8 == 0) { | |
| debug("Clean message $message_id"); | |
| output(); | |
| } else { | |
| error("Error in scanning process for message $message_id"); | |
| } | |
| ## Clean up tmpdir | |
| cleanup(); | |
| ## Clean exit | |
| exit 0; | |
| __END__ | |
| =head1 DESCRIPTION | |
| Exifilter is a pass-through filter (like i.e. SpamAssassin) which | |
| includes own headers into the mail. It means the mail is taken from | |
| stdin and given back to stdout. | |
| If virus was detected, the "X-Exifilter-Virus: YES" header is set. The | |
| detailed output of scanning process is included in "X-Exifilter-Detail:" | |
| headers. | |
| If scanner process is failed because of unexpected error, the mail is | |
| copied from stdin to stdout without modifications. | |
| Can be used as transport filter with Exim MTA, i.e.: | |
| transport_filter = /usr/bin/exifilter --scanner clamavd \ | |
| --pipe "spamc -u nobody" --debug-logfile \ | |
| $primary_hostname $message_id | |
| or: | |
| # Main section | |
| trusted_users = mail:www-data:exifilter | |
| # Exifilter transport | |
| exifilter_pipe: | |
| driver = pipe | |
| bsmtp = all | |
| command = /usr/sbin/exim -oMr exifilter-bsmtp -bS | |
| transport_filter = /usr/bin/exifilter --debug-logfile \ | |
| $primary_hostname $message_id | |
| home_directory = "/tmp" | |
| current_directory = "/tmp" | |
| # must use a privileged user to set $received_protocol on the way back in! | |
| user = exifilter | |
| group = exifilter | |
| prefix = | |
| suffix = | |
| log_output = true | |
| return_output = false | |
| return_fail_output = true | |
| return_path_add = false | |
| # Exifilter director | |
| exifilter_incoming: | |
| driver = smartuser | |
| verify = false | |
| expn = false | |
| condition = "${if !eq {$received_protocol}{exifilter-bsmtp}\ | |
| {1}{0}\ | |
| }" | |
| transport = exifilter_pipe | |
| # Exifilter router | |
| exifilter_outgoing: | |
| driver = domainlist | |
| route_list = "*" | |
| verify = false | |
| expn = false | |
| condition = "${if !eq {$received_protocol}{exifilter-bsmtp}\ | |
| {1}{0}\ | |
| }" | |
| transport = exifilter_pipe | |
| The integration with exim3conf is even simpler. In the | |
| F</etc/exim3conf/conf.d/local> file: | |
| EXIFILTER_FEATURE = true | |
| EXIFILTER_DEBUG_LOGFILE = true | |
| More sofisticated condition is also possible: | |
| EXIFILTER_INCOMING_LOCAL_CONDITION = eq {$domain}{just.for.this.example.com} | |
| If you want to filter out the virused messages, you can use system filter: | |
| The F<exim.conf> file: | |
| message_filter = /path/to/system_filter | |
| message_filter_directory_transport = address_directory | |
| And the F<system_filter> file: | |
| # Exim filter | |
| if $h_X-Exifilter-Virus is "YES" | |
| then | |
| logfile /var/log/exim/filterlog 0640 | |
| logwrite "$tod_log - filter: *** Exifilter *** - sender: $sender_address - | |
| subject: $h_subject: - id: $message_id - detail: $h_X-Exifilter-Detail" | |
| if $h_X-Exifilter-Detail does not match "(Worm|Trojan)" | |
| then | |
| save /var/spool/exifilter/ 0640 | |
| endif | |
| seen finish | |
| endif | |
| =head1 SECURITY | |
| Exifilter should be started with its own privileges on separated user | |
| and group. It requires log directory so you should create it with | |
| command: | |
| # adduser --system --group exifilter | |
| # install -d -m0750 -o exifilter -g adm /var/log/exifilter | |
| Exim runs system filter transport with its own privileges. In Debian | |
| distribution it is B<mail> user and group. If you want to save mails | |
| contained viruses, create it with command: | |
| # install -d -m0750 -o mail -g adm /var/spool/exifilter | |
| Exifilter temporary files are readable by group. It allows to permit the | |
| antivirus scanner daemon to read this files after adding the daemon user | |
| to the exifilter group. | |
| # adduser clamav exifilter | |
| =head1 OPTIONS | |
| =over 8 | |
| =item B<--no-cleanup> | |
| Don't clean up temporary directory. | |
| =item B<--debug-stderr> | |
| Turn on debug mode on stderr. | |
| =item B<--debug-logfile> | |
| Turn on debug mode on log file. | |
| =item B<--test> | |
| Alias for --no-cleanup --debug-stderr '$primary_hostname' '$message_id' | |
| =item S<B<--tmpdir> I<directory>> | |
| Localization of temporary directories. | |
| Default is F</tmp>. | |
| =item S<B<--logfile> I<path>> | |
| The path to log file in printf(3) format. The default is | |
| F</var/log/exifilter/%s.log>. | |
| =item S<B<--scanner> B<testok>|B<testvirus>|B<clamav>|B<clamavd>> | |
| Scanner used to scan for viruses. If set to B<testok>, the scanner is | |
| not used and no virus status is returned. If set to B<testvirus>, the | |
| scanner is not used and virus found status is returned. | |
| =item S<B<--timeout-scan> I<seconds>> | |
| Timeout for scan process. | |
| =item S<B<--pipe> I<command>> | |
| Output mail to I<command> through pipe than stdout. The arguments of commands | |
| are separated by space. | |
| =item B<-h>|B<--help> | |
| This help. | |
| =item I<primary_hostname> | |
| This specifies the name of the current host. The same as | |
| $primary_hostname variable in Exim MTA. If missing, the gethostname(2) | |
| function is used. | |
| =item I<message_id> | |
| The unique message id used for logging and archiving purposes. The same | |
| as $message_id variable in Exim MTA. If missing, the message id is | |
| generated based on unix time and actual PID. | |
| =back | |
| =head1 PROBLEMS | |
| If Exim reports "delivery filter process failed (2)", check if B<exifilter> | |
| is called with properly arguments (C<transport_filter> configuration option in | |
| F<exim.conf>). | |
| If Exim reports "delivery filter process failed (69)", check if path to | |
| B<exifilter> is correctly in C<transport_filter> configuration option. | |
| If Exim reports "defer (-25): Filter process failure", it means serious internal | |
| error, i.e. SIGSEGV. | |
| =head1 SEE ALSO | |
| B<exim>(8), B<clamscan>(1), B<clamdscan>(1) | |
| =head1 AUTHOR | |
| (c) 2003-2004 Piotr Roszatycki E<lt>dexter@debian.orgE<gt> | |
| All rights reserved. This program is free software; you can redistribute it | |
| and/or modify it under the terms of the GNU General Public License, the | |
| latest version. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment