From 167939748ceef3d98c469fa6574f8d47bc022cb8 Mon Sep 17 00:00:00 2001
From: John Peacock <jpeacock@cpan.org>
Date: Tue, 1 Mar 2005 20:11:09 +0000
Subject: [PATCH] *   Changes     Remember (belatedly) to add changes here

*   MANIFEST
    Add all new files to this list

*   plugins/virus/clamdscan
    New AV plugin to directly communicate with clamd daemon


git-svn-id: https://svn.perl.org/qpsmtpd/trunk@378 958fd67b-6ff1-0310-b445-bb7760255be9
---
 Changes                 |   7 ++
 MANIFEST                |   5 ++
 plugins/virus/clamdscan | 174 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 186 insertions(+)
 create mode 100644 plugins/virus/clamdscan

diff --git a/Changes b/Changes
index c208580..c928f09 100644
--- a/Changes
+++ b/Changes
@@ -1,6 +1,13 @@
 
 0.29
 
+  Store entire incoming message in spool file (so that scanners can read
+  the complete message) and ignore old headers before adding lines and
+  queuing for delivery.
+
+  New anti-virus scanners: hbedv (Hanno Hecker), bitdefender, and clamdscan
+  (John Peacock).  Update clamav plugin to directly scan the spool file.
+
   New temp_file() and temp_dir() methods; when used by plugins, they create
   a filename or directory which will last only as long as the current 
   transaction.  Also created a spool_dir() method which checks/creates the
diff --git a/MANIFEST b/MANIFEST
index efe3d40..6dfa5cf 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -40,6 +40,7 @@ plugins/content_log
 plugins/count_unrecognized_commands
 plugins/dnsbl
 plugins/dns_whitelist_soft
+plugins/greylisting
 plugins/http_config
 plugins/ident/geoip
 plugins/ident/p0f
@@ -55,8 +56,11 @@ plugins/rhsbl
 plugins/sender_permitted_from
 plugins/spamassassin
 plugins/virus/aveclient
+plugins/virus/bitdefender
 plugins/virus/check_for_hi_virus
 plugins/virus/clamav
+plugins/virus/clamdscan
+plugins/virus/hbedv
 plugins/virus/kavscanner
 plugins/virus/klez_filter
 plugins/virus/uvscan
@@ -75,4 +79,5 @@ t/plugin_tests.t
 t/plugin_tests/check_badrcptto
 t/plugin_tests/dnsbl
 t/Test/Qpsmtpd/Plugin.pm
+t/tempstuff.t
 META.yml                                 Module meta-data (added by MakeMaker)
diff --git a/plugins/virus/clamdscan b/plugins/virus/clamdscan
new file mode 100644
index 0000000..03a40e3
--- /dev/null
+++ b/plugins/virus/clamdscan
@@ -0,0 +1,174 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+clamdscan
+
+=head1 DESCRIPTION
+
+A qpsmtpd plugin for virus scanning using the ClamAV scan daemon, clamd.
+
+=head1 RESTRICTIONS
+
+The ClamAV scan daemon, clamd, must have at least read access to the
+qpsmtpd spool directory in order to sucessfully scan the messages.  You can
+ensure this by running clamd as the same user as qpsmtpd does (by far the
+easiest method) or by doing the following: 
+
+=over 4
+
+=item * Change the group ownership of the spool directory to be a group 
+of which clamav is a member or add clamav to the same group as the qpsmtpd
+user.
+
+=item * Enable the "AllowSupplementaryGroups" option in clamd.conf.
+
+=item * Change the permissions of the qpsmtpd spool directory to 0750 (this 
+will emit a warning when the qpsmtpd service starts up, but can be safely
+ignored).
+
+=item * Make sure that all directories above the spool directory (to the
+root) are g+x so that the group has directory traversal rights; it is not
+necessary for the group to have any read rights except to the spool
+directory itself.
+
+=back
+
+It may be helpful to temporary grant the clamav user a shell and test to
+make sure you can cd into the spool directory and read files located there.
+Remember to remove the shell from the clamav user when you are done
+testing.
+
+=head1 INSTALL AND CONFIG
+
+Place this plugin in the plugin/virus directory beneath the standard
+qpsmtpd installation.  If you installed clamd with the default path, you
+can use this plugin with default options (nothing specified):
+
+=over 4
+
+=item B<clamd_socket>
+
+Full path to the clamd socket (the recommended mode); defaults to
+/tmp/clamd and is the default method.
+
+=item B<clamd_port>
+
+If present, must be the TCP port where the clamd service is running,
+typically 3310; default disabled.
+
+=item B<deny_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
+a header to the message with the virus results.
+
+=item B<max_size>
+
+The maximum size, in kilobytes, of messages to scan; defaults to 128k.
+
+=back
+
+=head1 REQUIREMENTS
+
+This module requires the Clamd module, found on CPAN here:
+
+L<http://search.cpan.org/author/MSERGEANT/Clamd-1.04>
+
+=head1 AUTHOR
+
+John Peacock <jpeacock@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2005 John Peacock
+
+Based heavily on the clamav plugin
+
+This plugin is licensed under the same terms as the qpsmtpd package itself.
+Please see the LICENSE file included with qpsmtpd for details.
+
+=cut
+
+use Clamd;
+
+sub register {
+    my ( $self, $qp, @args ) = @_;
+    $self->register_hook( "data_post", "clamdscan" );
+
+    %{ $self->{"_clamd"} } = @args;
+
+    # Set some sensible defaults
+    $self->{"_clamd"}->{"clamd_socket"} ||= "/tmp/clamd";
+    $self->{"_clamd"}->{"deny_viruses"} ||= "yes";
+    $self->{"_clamd"}->{"max_size"}     ||= 128;
+}
+
+sub clamdscan {
+    my ( $self, $transaction ) = @_;
+    $DB::single = 1;
+
+    if ( $transaction->body_size > $self->{"_clamd"}->{"max_size"} * 1024 ) {
+        $self->log( LOGNOTICE, "Declining due to body_size" );
+        return (DECLINED);
+    }
+
+    # Ignore non-multipart emails
+    my $content_type = $transaction->header->get('Content-Type');
+    $content_type =~ s/\s/ /g if defined $content_type;
+    unless ( $content_type
+        && $content_type =~ m!\bmultipart/.*\bboundary="?([^"]+)!i )
+    {
+        $self->log( LOGERROR, "non-multipart mail - skipping" );
+        return DECLINED;
+    }
+
+    my $filename = $transaction->body_filename;
+    unless ($filename) {
+        $self->log( LOGWARN, "Cannot process due to lack of filename" );
+        return (DECLINED);    # unless $filename;
+    }
+
+    my $mode = ( stat( $self->spool_dir() ) )[2];
+    if ( $mode & 07077 ) {   # must be sharing spool directory with external app
+        $self->log( LOGWARN,
+            "Changing permissions on file to permit scanner access" );
+        chmod $mode, $filename;
+    }
+
+    my $clamd;
+
+    if (    $self->{"_clamd"}->{"clamd_port"}
+        and $self->{"_clamd"}->{"clamd_port"} =~ /(\d+)/ )
+    {
+        my $port = $1;
+        $clamd = Clamd->new( port => $port );
+    }
+    else {
+        $clamd = Clamd->new();    # default unix domain socket
+    }
+
+    return (DECLINED) unless $clamd->ping();
+
+    if ( my %found = $clamd->scan($filename) ) {
+        my $viruses = join( ",", values(%found) );
+	$self->log( LOGERROR, "One or more virus(es) found: $viruses" );
+
+        if ( lc( $self->{"_clamd"}->{"deny_viruses"} ) eq "yes" ) {
+            return ( DENY,
+                    "Virus"
+                  . ( $viruses =~ /,/ ? "es " : " " )
+                  . "Found: $viruses" );
+        }
+        else {
+            $transaction->header->add( 'X-Virus-Found',   'Yes' );
+            $transaction->header->add( 'X-Virus-Details', $viruses );
+            return (DECLINED);
+        }
+    }
+
+    $transaction->header->add( 'X-Virus-Checked',
+        "Checked by ClamAV on " . $self->qp->config("me") );
+
+    return (DECLINED);
+}