File Coverage

blib/lib/File/VirusScan/Engine/Daemon/Symantec/CSS.pm
Criterion Covered Total %
statement 33 125 26.4
branch 3 46 6.5
condition 2 7 28.5
subroutine 10 15 66.6
pod 2 2 100.0
total 50 195 25.6


line stmt bran cond sub pod time code
1             package File::VirusScan::Engine::Daemon::Symantec::CSS;
2 1     1   96369 use strict;
  1         7  
  1         22  
3 1     1   5 use warnings;
  1         1  
  1         19  
4 1     1   3 use Carp;
  1         2  
  1         58  
5              
6 1     1   372 use File::VirusScan::Engine::Daemon;
  1         3  
  1         7  
7 1     1   43 use vars qw( @ISA );
  1         2  
  1         39  
8             @ISA = qw( File::VirusScan::Engine::Daemon );
9              
10 1     1   375 use IO::Socket::INET;
  1         10302  
  1         5  
11 1     1   416 use Cwd 'abs_path';
  1         2  
  1         45  
12              
13 1     1   338 use File::VirusScan::Result;
  1         2  
  1         997  
14              
15             sub new
16             {
17 8     8 1 15283 my ($class, $conf) = @_;
18              
19 8 100       18 if(!$conf->{host}) {
20 1         11 croak "Must supply a 'host' config value for $class";
21             }
22              
23             my $self = {
24             host => $conf->{host},
25             port => $conf->{port} || 7777,
26 7   50     37 is_local => $conf->{is_local} || 1,
      50        
27             };
28              
29 7         28 return bless $self, $class;
30             }
31              
32             sub _get_socket
33             {
34 5     5   6404 my ($self) = @_;
35              
36             my $sock = IO::Socket::INET->new(
37             PeerAddr => $self->{host},
38             PeerPort => $self->{port},
39 5         26 );
40 5 50       1964 if(!defined $sock) {
41 5         78 croak "Error: Could not connect to CarrierScan Server on $self->{host}, port $self->{port}: $!";
42             }
43              
44             # First reply line should be 220 code
45 0           my $line = _read_line($sock);
46 0 0         unless ($line =~ /^220/) {
47 0           croak "Error: Unexpected reply $line from CarrierScan Server";
48             }
49              
50             # Next line must be version
51 0           $line = _read_line($sock);
52 0 0         unless ($line eq '2') {
53 0           croak "Error: Unexpected version $line from CarrierScan Server";
54             }
55              
56             # OK, probably fine to use this sock
57 0           return $sock;
58             }
59              
60             sub scan
61             {
62 0     0 1   my ($self, $path) = @_;
63              
64 0           my $abs = abs_path($path);
65 0 0 0       if ($abs && $abs ne $path) {
66 0           $path = $abs;
67             }
68              
69 0           my @files = eval { $self->list_files($path) };
  0            
70 0 0         if($@) {
71 0           return File::VirusScan::Result->error($@);
72             }
73              
74 0           foreach my $file_path (@files) {
75             my $result
76             = $self->{is_local}
77 0 0         ? $self->_scan_local($file_path)
78             : $self->_scan_remote($file_path);
79 0 0         if(!$result->is_clean()) {
80 0           return $result;
81             }
82             }
83             }
84              
85             sub _scan_local
86             {
87 0     0     my ($self, $path) = @_;
88              
89 0           my $sock = eval { $self->_get_socket };
  0            
90 0 0         if($@) {
91 0           return File::VirusScan::Result->error($@);
92             }
93              
94 0 0         if(!$sock->print("Version2\nAVSCANLOCAL\n$path\n")) {
95 0           my $err = $!;
96 0           $sock->close;
97 0           return File::VirusScan::Result->error("Could not write to socket: $err");
98             }
99              
100 0 0         if(!$sock->flush) {
101 0           my $err = $!;
102 0           $sock->close;
103 0           return File::VirusScan::Result->error("Error flushing socket: $err");
104             }
105              
106 0           my $result = $self->_parse_server_response($sock);
107 0           $sock->close;
108 0           return $result;
109             }
110              
111             sub _scan_remote
112             {
113 0     0     my ($self, $path) = @_;
114              
115 0           my $size = (stat($path))[7];
116 0 0         unless (defined($size)) {
117 0           return File::VirusScan::Result->error("Cannot stat $path: $!");
118             }
119              
120 0           my $sock = eval { $self->_get_socket };
  0            
121 0 0         if($@) {
122 0           return File::VirusScan::Result->error($@);
123             }
124              
125 0 0         if(!$sock->print("Version2\nAVSCAN\n$path\n$size\n")) {
126 0           my $err = $!;
127 0           $sock->close;
128 0           return File::VirusScan::Result->error("Could not write to socket: $err");
129             }
130              
131 0           my $fh = IO::File->new("<$path");
132 0 0         if(!$fh) {
133 0           return File::VirusScan::Result->error("Cannot open $path: $!");
134             }
135              
136             # Write file to socket
137 0           while ($size > 0) {
138 0 0         my $chunksize
139             = ($size < 8192)
140             ? $size
141             : 8192;
142              
143 0           my $chunk;
144 0           my $nread = $fh->read($chunk, $chunksize);
145 0 0         unless (defined $nread) {
146 0           my $err = $!;
147 0           $sock->close;
148 0           return File::VirusScan::Result->error("Error reading $path: $err");
149             }
150              
151 0 0         last if($nread == 0);
152              
153 0 0         if(!$sock->print($chunk)) {
154 0           my $err = $!;
155 0           $sock->close;
156 0           return File::VirusScan::Result->error("Error writing to socket: $err");
157             }
158              
159 0           $size -= $nread;
160             }
161              
162 0 0         if($size > 0) {
163 0           my $err = $!;
164 0           $sock->close;
165 0           return File::VirusScan::Result->error("Error reading $path: $err");
166             }
167              
168 0 0         if(!$sock->flush) {
169 0           my $err = $!;
170 0           $sock->close;
171 0           return File::VirusScan::Result->error("Error flushing socket: $err");
172             }
173              
174 0           my $result = $self->_parse_server_response($sock);
175 0           $sock->close;
176 0           return $result;
177             }
178              
179             sub _parse_server_response
180             {
181 0     0     my ($self, $sock) = @_;
182              
183             # Get reply from server
184 0           my $line = _read_line($sock);
185              
186 0 0         unless ($line =~ /^230/) {
187 0           return File::VirusScan::Result->error("Unexpected response to AVSCAN or AVSCANLOCAL command: $line");
188             }
189              
190             # Read infection status
191 0           $line = _read_line($sock);
192 0 0         if($line eq '0') {
193 0           return File::VirusScan::Result->clean();
194             }
195              
196             # Skip next four lines:
197             # - definition date
198             # - definition version
199             # - infection count
200             # - filename
201 0           $line = _read_line($sock);
202 0           $line = _read_line($sock);
203 0           $line = _read_line($sock);
204 0           $line = _read_line($sock);
205              
206             # Get virus name
207 0           $line = _read_line($sock);
208              
209 0           return File::VirusScan::Result->virus($line);
210             }
211              
212             sub _read_line
213             {
214 0     0     my ($sock) = @_;
215              
216 0           chomp(my $line = $sock->getline);
217 0           $line =~ s/\r//g;
218 0           return $line;
219             }
220              
221             1;
222             __END__