File Coverage

blib/lib/File/VirusScan/Engine/Daemon/Symantec/CSS.pm
Criterion Covered Total %
statement 36 124 29.0
branch 4 46 8.7
condition 2 4 50.0
subroutine 11 15 73.3
pod 2 2 100.0
total 55 191 28.8


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