File Coverage

blib/lib/Mail/IspMailGate/Filter/VirScan.pm
Criterion Covered Total %
statement 100 121 82.6
branch 19 34 55.8
condition 3 3 100.0
subroutine 14 15 93.3
pod 7 9 77.7
total 143 182 78.5


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3              
4              
5 4     4   2722 use strict;
  4         9  
  4         153  
6             require 5.005;
7              
8 4     4   4270 use File::Copy ();
  4         1495213  
  4         116  
9 4     4   36 use File::Basename ();
  4         12  
  4         65  
10 4     4   24 use Symbol ();
  4         10  
  4         71  
11 4     4   26 use Mail::IspMailGate::Filter ();
  4         9  
  4         136  
12 4     4   23 use File::Spec ();
  4         7  
  4         7871  
13              
14              
15             package Mail::IspMailGate::Filter::VirScan;
16              
17             @Mail::IspMailGate::Filter::VirScan::ISA = qw(Mail::IspMailGate::Filter);
18              
19 48     48 1 571 sub getSign { "X-ispMailGate-VirScan" }
20              
21             #####################################################################
22             #
23             # Name: mustFilter
24             #
25             # Purpose: determines wether this message must be filtered and
26             # allowed to modify $self the message and so on
27             #
28             # Inputs: $self - This class
29             # $entity - the whole message
30             #
31             #
32             # Returns: 1 if it must be, else 0
33             #
34             #####################################################################
35              
36             sub mustFilter ($$) {
37             # Always true (consider faked headers!)
38 10     10 1 42 1;
39             }
40              
41              
42             #####################################################################
43             #
44             # Name: hookFilter
45             #
46             # Purpose: a function which is called after the filtering process
47             #
48             # Inputs: $self - This class
49             # $entity - the whole message
50             #
51             #
52             # Returns: errormessage if any
53             #
54             #####################################################################
55              
56             sub hookFilter ($$) {
57 10     10 1 30 my($self, $entity) = @_;
58 10         112 my($head) = $entity->head;
59 10         150 $head->set($self->getSign(), 'scanned');
60 10         4324 '';
61             }
62              
63              
64              
65             #####################################################################
66             #
67             # Name: createDir
68             #
69             # Purpse: creates a new directory, under the given
70             #
71             # Inputs: $self - This class
72             # $attr - Attributes
73             #
74             # Returns: the name of the new dir
75             #
76             #####################################################################
77              
78             sub createDir ($$) {
79 8     8 0 36 my ($self, $attr) = @_;
80              
81 8         200 my($baseDir) = $attr->{'parser'}->output_dir();
82 8         758 my($i) = 0;
83 8         28 my($dir);
84              
85 8         195 while (-e ($dir = "$baseDir/dir$i")) {
86 92         2237 ++$i;
87             }
88 8 50       82101 if (!mkdir $dir, 0700) {
89 0         0 die "Cannot create directory $dir ($!)";
90             }
91 8         218 $dir;
92             }
93              
94              
95             #####################################################################
96             #
97             # Name: checkDirFiles
98             #
99             # Purpse: creates a list of files from a certain directory,
100             # including subdirectories
101             #
102             # Inputs: $self - This instance
103             # $dir - Directory name
104             #
105             # Returns: File list; dies in case of trouble
106             #
107             #####################################################################
108              
109             sub checkDirFiles ($$) {
110 18     18 0 137 my($self, $dir) = @_;
111 18         44 my(@files);
112              
113             #
114             # Recursively scan directory $dir for files
115             #
116 18         232 my($dirHandle) = Symbol::gensym();
117 18 50       1843 if (!opendir($dirHandle, $dir)) {
118 0         0 die "Cannot read directory $dir ($!)";
119             }
120 18         58 my($file);
121 18         998 while (defined($file = readdir($dirHandle))) {
122 132 100 100     983 if ($file eq '.' || $file eq '..') {
123 36         658 next;
124             }
125 96         821 $file = "$dir/$file";
126 96 100       2099 if (-d $file) {
    50          
127 10         61 push(@files, $self->checkDirFiles($file));
128             } elsif (-f _) {
129 86         577 push(@files, $file);
130             }
131             }
132 18         235 closedir($dirHandle);
133              
134 18         489 @files;
135             }
136              
137              
138             #####################################################################
139             #
140             # Name: checkArchive
141             #
142             # Purpse: creates a new temporary directory and extracts an
143             # archive into it; returns a list of files that have
144             # been created by calling checkDirFiles
145             #
146             # Inputs: $self - This instance
147             # $attr - The $attr argument of filterList
148             # $ipath - The archive path
149             # $deflater - An element from the deflater list that
150             # matches $ipath.
151             #
152             # Returns: File list; dies in case of trouble
153             #
154             #####################################################################
155              
156             sub checkArchive ($$$$) {
157 8     8 1 35 my($self, $attr, $ipath, $deflater) = @_;
158 8         31 my $cfg = $Mail::IspMailGate::Config::config;
159              
160             # Create a new directory for extracting the files into it.
161 8         1745 my %patterns = ('ipath' => $ipath,
162             'idir' => File::Basename::dirname($ipath),
163             'ifile' => File::Basename::basename($ipath),
164             'odir' => $self->createDir($attr));
165 8         33 $patterns{'ofile'} = $patterns{'ifile'};
166 8 50       68 if ($deflater->{'extension'}) {
167 0         0 $patterns{'ofile'} =~ s/$deflater->{'extension'}$//;
168             }
169 8         335 $patterns{'opath'} = File::Spec->catfile($patterns{'odir'},
170             $patterns{'ofile'});
171 8         44 my $cmd = $deflater->{'cmd'};
172 8 50       113 $cmd =~ s/\$(\w+)/exists($patterns{$1}) ? quotemeta($patterns{$1}) :
  32 100       476  
173             exists $cfg->{$1} ? $cfg->{$1} : ''/esg;
174 8         268 $attr->{'main'}->Debug("Running command: $cmd");
175 8         787661 system $cmd;
176              
177 8         901 $self->checkDirFiles($patterns{'odir'});
178             }
179              
180              
181             ############################################################################
182             #
183             # Name: HasVirus
184             #
185             # Purpose: Takes the virus scanners output and parses it for virus
186             # warnings.
187             #
188             # This version is well suited for the AntiVir virus scanner.
189             # You typically need to override it for other programs.
190             #
191             # Input: $self - Instance
192             # $output - Message emitted by the virus scanner
193             #
194             # Returns: TRUE if $output indicates a virus, FALSE otherwise
195             #
196             ############################################################################
197              
198             sub HasVirus {
199 0     0 1 0 my $self = shift; my $str = shift;
  0         0  
200 0         0 my $result = join('\n', grep { $_ =~ /\!Virus\!/ } split(/\n/, $str));
  0         0  
201 0 0       0 $result ? "Alert: A Virus has been detected:\n\n$result\n" : '';
202             }
203              
204              
205             #####################################################################
206             #
207             # Name: checkFile
208             #
209             # Purpse: checks a file (recursively if archive) for virus
210             #
211             # Inputs: $self - Instance
212             # $attr - Same as the $attr argument of filterFile
213             # $ipath - the file to check
214             #
215             # Returns: error message, if any
216             #
217             #####################################################################
218              
219             sub checkFile ($$$) {
220 38     38 1 92 my ($self, $attr, $ipath) = @_;
221 38         158 my(@simpleFiles, @checkFiles);
222 38         86 my($ret) = '';
223 38         56 my $cfg = $Mail::IspMailGate::Config::config;
224              
225 38         969 @checkFiles = ($ipath);
226 38         304 my($file);
227 38         132 while (defined($file = shift @checkFiles)) {
228             # Modify the name for use in a shell command
229 124 50       1096 if ($file =~ /[\000-\037]/) {
230 0         0 $ret .= "Suspect file names: $file";
231 0         0 next;
232             }
233              
234             # Check whether file is an archive
235 124         164 my($deflater);
236 124         165 foreach $deflater (@{$cfg->{'virscan'}->{'deflater'}}) {
  124         570  
237 356 100       16347 if ($file =~ /$deflater->{'pattern'}/) {
238 8         165 push(@checkFiles,
239             $self->checkArchive($attr, $file, $deflater));
240 8         115 undef $file;
241 8         47 last;
242             }
243             }
244              
245             # If it isn't, scan it
246 124 100       473 if (defined($file)) {
247 116         398 push(@simpleFiles, $file);
248             }
249             }
250              
251 38 50       120 if (@simpleFiles) {
252 38         171 my $cmd = $cfg->{'virscan'}->{'scanner'};
253 38         489 $cmd =~ s/\$antivir_path/$cfg->{'antivir_path'}/g;
254 38         66 my $output;
255 38 50       415 if ($cmd =~ /\$ipaths/) {
256             # We may scan all files with a single command
257 38         429 my($ipaths) = '';
258 38         86 foreach $file (@simpleFiles) {
259 116         1245 $ipaths .= ' ' . quotemeta($file);
260             }
261 38         267 $cmd =~ s/\$ipaths/$ipaths/sg;
262 38 0       88 $cmd =~ s/\$(\w+)/exists $cfg->{$1} ? $cfg->{$1} : ''/seg;
  0         0  
263 38         348 $attr->{'main'}->Debug("Running command: $cmd");
264 38         2474824 $output = `$cmd`;
265 38         2596 $ret .= $self->HasVirus($output);
266             } else {
267             # We need to scan any file separately
268 0         0 foreach $file (@simpleFiles) {
269 0         0 $ipath = quotemeta($file);
270 0         0 $cmd =~ s/\$ipath/$ipath/sg;
271 0 0       0 $cmd =~ s/\$(\w+)/exists $cfg->{$1} ? $cfg->{$1} : ''/seg;
  0         0  
272 0         0 $attr->{'main'}->Debug("Running command: $cmd");
273 0         0 $output = `$cmd`;
274 0         0 $ret .= $self->HasVirus($output);
275             }
276             }
277             }
278 38         3015 $ret;
279             }
280              
281              
282             #####################################################################
283             #
284             # Name: filterFile
285             #
286             # Purpse: do the filter process for one file
287             #
288             # Inputs: $self - This class
289             # $attr - hash-ref to filter attribute
290             # 1. 'body'
291             # 2. 'parser'
292             # 3. 'head'
293             # 4. 'globHead'
294             #
295             # Returns: error message, if any
296             #
297             #####################################################################
298              
299             sub filterFile ($$) {
300 38     38 1 219 my ($self, $attr) = @_;
301 38         88 my $cfg = $Mail::IspMailGate::Config::config;
302              
303 38         146 my ($body) = $attr->{'body'};
304 38         76 my ($globHead) = $attr->{'globHead'};
305 38         123 my ($ifile) = $body->path();
306 38         512 $attr->{'main'}->Debug("Scanning file $ifile for viruses");
307 38         86 my ($ret) = 0;
308 38 50       876 if($ret = $self->SUPER::filterFile($attr)) {
309 0         0 $attr->{'main'}->Debug("Returning immediately, result $ret");
310 0         0 return $ret;
311             }
312              
313 38         183 $ret = $self->checkFile($attr, $ifile);
314 38         1517 $attr->{'main'}->Debug("Returning, result $ret");
315 38         448 $ret;
316             }
317              
318              
319             1;
320              
321             __END__