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