File Coverage

blib/lib/File/VirusScan/Engine/Daemon/FPROT/V6.pm
Criterion Covered Total %
statement 35 85 41.1
branch 5 36 13.8
condition 5 14 35.7
subroutine 10 12 83.3
pod 2 2 100.0
total 57 149 38.2


line stmt bran cond sub pod time code
1             package File::VirusScan::Engine::Daemon::FPROT::V6;
2 1     1   60772 use strict;
  1         1  
  1         34  
3 1     1   5 use warnings;
  1         2  
  1         27  
4 1     1   5 use Carp;
  1         3  
  1         74  
5              
6 1     1   413 use File::VirusScan::Engine::Daemon;
  1         2  
  1         7  
7 1     1   31 use vars qw( @ISA );
  1         1  
  1         31  
8             @ISA = qw( File::VirusScan::Engine::Daemon );
9              
10 1     1   437 use IO::Socket::INET;
  1         9077  
  1         7  
11 1     1   636 use Cwd 'abs_path';
  1         1  
  1         54  
12 1     1   455 use File::VirusScan::Result;
  1         2  
  1         831  
13              
14             sub new
15             {
16 7     7 1 10133 my ($class, $conf) = @_;
17              
18 7 100       21 if(!$conf->{host}) {
19 1         14 croak "Must supply a 'host' config value for $class";
20             }
21              
22 6         6 my $port;
23 6 50       15 if($conf->{host}) {
24 6 50       18 if($conf->{host} =~ s/:(\d+)\Z//) {
25 0         0 $port = $1;
26             }
27             }
28 6   50     99 my $self = {
      50        
      50        
      50        
      50        
29             host => $conf->{host} || 127.0.0.1,
30             port => $conf->{port} || $port || 10200,
31             connect_timeout => $conf->{connect_timeout} || 10,
32             read_timeout => $conf->{read_timeout} || 60,
33             options => $conf->{options} || [
34              
35             # Instructs the Daemon Scanner which scanlevel to use:
36             # 0 => Disable regular scanning (only heuristics).
37             # 1 => Skip suspicious data files. Not recommended if filename is unavailable.
38             # 2 => (Default) Unknown and/or wrong extensions will be emulated.
39             # 3 => Unknown binaries emulated.
40             # 4 => For scanning virus collections, no limits for emulation
41             '--scanlevel=2',
42              
43             # archive depth
44             '--archive=2',
45              
46             # How aggressive heuristic should be used, 0..4
47             # the higher the more heuristic tests are done which increases
48             # both detection rates AND risk of false positives.
49             '--heurlevel=2',
50              
51             # to flag adware
52             '--adware',
53              
54             # to flag potentially unwanted applications
55             '--applications',
56             ],
57             };
58              
59 6         29 return bless $self, $class;
60             }
61              
62             sub scan
63             {
64 1     1 1 1075 my ($self, $path) = @_;
65              
66 1 50       35 if(abs_path($path) ne $path) {
67 1         11 return File::VirusScan::Result->error("Path $path is not absolute");
68             }
69              
70             # The F-Prot demon cannot scan directories, but files only
71             # hence, we recurse any directories manually
72 0           my @files = eval { $self->list_files($path) };
  0            
73 0 0         if($@) {
74 0           return File::VirusScan::Result->error($@);
75             }
76              
77 0           foreach my $file_path (@files) {
78 0           my $result = $self->_scan($file_path);
79              
80 0 0         if(!$result->is_clean()) {
81 0           return $result;
82             }
83             }
84              
85             }
86              
87             # Scans a single path.
88             sub _scan
89             {
90 0     0     my ($self, $path) = @_;
91              
92 0           my $sock = eval { $self->_get_socket };
  0            
93 0 0         if($@) {
94 0           return File::VirusScan::Result->error($@);
95             }
96              
97             # Stringify our options
98 0           my $options = join(' ', $self->{options});
99              
100             # SCAN options FILE fnam\n (local daemon)
101             # -or- SCAN options STREAM fnam SIZE length\n (remote daemon)
102             # length bytes of data
103             # assume local daemon ==> implement FILE variant only
104             # , supports spaces in fnam
105 0 0         if(!$sock->print("SCAN $options FILE $path\n")) {
106 0           my $err = $!;
107 0           $sock->close;
108 0           return File::VirusScan::Result->error("Could not write to socket: $err");
109             }
110              
111 0 0         if(!$sock->flush) {
112 0           my $err = $!;
113 0           $sock->close;
114 0           return File::VirusScan::Result->error("Could not flush socket: $err");
115             }
116              
117 0           my $s = IO::Select->new($sock);
118 0 0         if(!$s->can_read($self->{read_timeout})) {
119 0           $sock->close;
120 0           return File::VirusScan::Result->error("Timeout reading from fprot daemon");
121             }
122              
123 0           my $resp = $sock->getline;
124 0 0         if(!$resp) {
125 0           $sock->close;
126 0           return File::VirusScan::Result->error("Did not get response from fprot while scanning $path");
127             }
128              
129 0           my ($code, $desc, $name);
130 0 0         unless (($code, $desc, $name) = $resp =~ /\A(\d+)\s<(.*?)>\s(.*)\Z/) {
131 0           return File::VirusScan::Result->error("Failed to parse response from fprotd: $path");
132             }
133              
134             # Clean up $desc
135 0           $desc =~ s/\A(?:contains infected objects|infected):\s//i;
136              
137             # Our output should contain:
138             # 1) A code. The code is a bitmask of:
139             # bit num Meaning
140             # 0 1 At least one virus-infected object was found (and remains).
141             # 1 2 At least one suspicious (heuristic match) object was found (and remains).
142             # 2 4 Interrupted by user. (SIGINT, SIGBREAK).
143             # 3 8 Scan restriction caused scan to skip files (maxdepth directories, maxdepth archives, exclusion list, etc).
144             # 4 16 Platform error (out of memory, real I/O errors, insufficient file permission etc.).
145             # 5 32 Internal engine error (whatever the engine fails at)
146             # 6 64 At least one object was not scanned (encrypted file, unsupported/unknown compression method, corrupted or invalid file).
147             # 7 128 At least one object was disinfected (clean now) (treat same as virus for File::VirusScan)
148             #
149             # 2) The description, including virus name
150             #
151             # 3) The item name, incl. member of archive etc. We ignore
152             # this for now.
153              
154 0 0         if($code & (1 | 128)) {
    0          
    0          
    0          
    0          
155 0           my $virus_name = $desc;
156 0   0       $virus_name ||= 'unknown-FPROTD-virus';
157 0           return File::VirusScan::Result->virus($virus_name);
158             } elsif($code & 2) {
159 0           my $virus_name = $desc;
160 0   0       $virus_name ||= 'unknown-FPROTD-virus';
161 0           return File::VirusScan::Result->virus($virus_name);
162             } elsif($code & 4) {
163 0           return File::VirusScan::Result->error('FPROTD scanning interrupted by user');
164             } elsif($code & 16) {
165 0           return File::VirusScan::Result->error('FPROTD platform error');
166             } elsif($code & 32) {
167 0           return File::VirusScan::Result->error('FPROTD internal engine error');
168             }
169              
170 0           return File::VirusScan::Result->clean();
171             }
172              
173             # Returns preconfigured socket, or opens a new connection.
174             sub _get_socket
175             {
176 0     0     my ($self) = @_;
177              
178 0 0         if(!$self->{sock}) {
179 0           $self->{sock} = IO::Socket::INET->new(
180             PeerAddr => $self->{host},
181             PeerPort => $self->{port},
182             Timeout => $self->{connect_timeout},
183             );
184             }
185              
186 0           return $self->{sock};
187             }
188              
189             1;
190             __END__