1#!perl -w 2 3=head1 NAME 4 5aveclient 6 7=head1 DESCRIPTION 8 9This qpsmtpd plugin uses the aveclient of a kaspersky 5.x server-suite. The original kaspersky 10aveclient is called within this plugin to connect to the local socket of the aveserver. 11The aveserver runs as a daemon with all virusdefinitions already loaded, what makes scanning veeery 12quick and performant without much load. 13 14When a virus is detected, the mail is blocked and the connection is denied! Further configuration 15is simple to be added. 16 17=head1 INSTALL AND CONFIG 18 19Place this plugin in the default plugin directory of your qpsmtpd installation. Normaly you can use 20it with default options (nothing specified): 21 22=over 4 23 24=item B<aveclient> 25 26Optional you may set the path to original aveclient and/or the socket: 27 28=over 4 29 30=item avclient_bin I</path/to/ave/binary> 31 32Set the path to the original aveclient of kaspersky 5.x server-suite. 33Default: /opt/kav/bin/aveclient 34 35=item avdaemon_sock I</path/to/socket> 36 37Set the path to the unix socket of the original aveserver of kaspersky 5.x server-suite. 38Default: /var/run/aveserver 39 40=item blockonerror I<(1|0)> 41 42Whether to block mails on scanning errors or to accept connections. 43Default: 0 (No) 44 45=back 46 47=back 48 49=head1 EXIT CODES OF aveclient (taken from man aveclient) 50 51When launched with the -s option, aveclient returns one of the following codes (if several files to be scanned are indicated in the 52command line, the return code corresponds to the results of scanning the last file): 53 540 no viruses have been detected. 55 561 unable to connect to aveserver. 57 582 objects with an unknown viral code have been found. 59 603 suspicious objects have been found. 61 624 infected objects have been detected. 63 645 all infected objects have been disinfected. 65 666 scan results are unavailable: encrypted or password protected file. 67 687 system error launching the application (file not found, unable to read the file). 69 708 scan results are unavailable: file is corrupted or input/output error. 71 729 some of the required parameters are missing from the command line. 73 74=head1 VERSION 75 760.1rc first proof of concept. 77How is load and performance on larger systems? This is tested whith aprox. 900 Clients 78on a small RH-System (AMD, 768 Mhz, 512 MB) MAXCLIENTS set to 40. 79 80=head1 AUTHOR 81 82Adopted by Marcus Spiegel <aveclient@uscreen.de> from kavscanner plugin of Hanno Hecker. 83 84THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 85IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 86FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 87AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 88LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 89OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 90SOFTWARE. 91 92=cut 93 94use File::Temp qw(tempfile); 95use Mail::Address; 96 97sub register { 98 my ($self, $qp, @args) = @_; 99 100 # defaults to be used 101 $self->{_avclient_bin} = "/opt/kav/bin/aveclient"; 102 $self->{_avdaemon_sock} = "/var/run/aveserver"; 103 $self->{_blockonerror} = 0; 104 105 # parse optional arguments 106 my %args = @args; 107 foreach my $key (keys %args) { 108 my $arg = $key; 109 $key =~ s/^/_/; 110 $self->{$key} = $args{$arg}; 111 } 112 113 # Untaint client location 114 # socket will be tested during scan (response-code) 115 if (exists $self->{_avclient_bin} 116 && $self->{_avclient_bin} =~ /^(\/[\/\-\_\.a-z0-9A-Z]*)$/) 117 { 118 $self->{_avclient_bin} = $1; 119 } 120 else { 121 $self->log(LOGALERT, 122 "FATAL ERROR: No binary aveclient found: '" 123 . $self->{_avclient_bin} . "'" 124 ); 125 exit 3; 126 } 127} 128 129sub hook_data_post { 130 my ($self, $transaction) = @_; 131 my ($temp_fh, $filename) = tempfile(); 132 my $description = 'clean'; 133 134 # a temporary file is needed to be scanned 135 print $temp_fh $transaction->header->as_string; 136 print $temp_fh "\n"; 137 138 $transaction->body_resetpos; 139 140 while (my $line = $transaction->body_getline) { 141 print $temp_fh $line; 142 } 143 seek($temp_fh, 0, 0); 144 145 # Now scan this file 146 my $cmd = 147 $self->{_avclient_bin} . " -p " 148 . $self->{_avdaemon_sock} 149 . " -s $filename 2>&1"; 150 151 my @output = `$cmd`; 152 chomp(@output); 153 154 my $result = ($? >> 8); 155 my $signal = ($? & 127); 156 157 # tidy up a bit 158 unlink($filename); 159 close $temp_fh; 160 161 # check if something went wrong 162 if ($signal) { 163 $self->log(LOGERROR, "kavscanner exited with signal: $signal"); 164 return (DECLINED); 165 } 166 167 # either we found a virus or something went wrong 168 if ($result > 0) { 169 if ($result =~ /^(2|3|4|6|8)$/) { 170 171 # ok a somewhat virus was found 172 shift @output; 173 $description = "REPORT: " . join(", ", @output); 174 $self->log(LOGWARN, "Virus found! ($description)"); 175 176 # we don't want to be disturbed be these, so block mail and DENY connection 177 return (DENY, "Virus found: $description"); 178 179 } 180 else { 181 $self->log(LOGCRIT, "aveserver: no viruses have been detected.") 182 if ($result =~ /^0$/); 183 $self->log(LOGCRIT, 184"aveserver: system error launching the application (file not found, unable to read the file)." 185 ) 186 if ($result =~ /^0$/); 187 $self->log(LOGCRIT, 188"aveserver: some of the required parameters are missing from the command line." 189 ) 190 if ($result =~ /^9$/); 191 return (DENY, 192 "Unable to scan for virus, please contact admin of " 193 . $self->qp->config("me") 194 . ", if you feel this is an error!" 195 ) 196 if $self->{_blockonerror}; 197 } 198 } 199 200 $self->log(LOGINFO, "kavscanner results: $description"); 201 $transaction->header->add('X-Virus-Checked', 202 'Checked by Kaspersky on ' . $self->qp->config("me")); 203 return (DECLINED); 204} 205