File Coverage

blib/lib/FusionInventory/Agent/Task/Collect.pm
Criterion Covered Total %
statement 98 217 45.1
branch 36 130 27.6
condition 11 33 33.3
subroutine 18 26 69.2
pod 2 2 100.0
total 165 408 40.4


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::Task::Collect;
2              
3 3     3   17655764 use strict;
  3         7  
  3         164  
4 3     3   38 use warnings;
  3         7  
  3         204  
5 3     3   27 use base 'FusionInventory::Agent::Task';
  3         106  
  3         2424  
6              
7 3     3   2699 use Digest::SHA;
  3         11599  
  3         200  
8 3     3   20 use English qw(-no_match_vars);
  3         6  
  3         19  
9 3     3   1399 use File::Basename;
  3         5  
  3         188  
10 3     3   16 use File::Find;
  3         5  
  3         151  
11 3     3   16 use File::stat;
  3         5  
  3         20  
12              
13 3     3   1972 use FusionInventory::Agent;
  3         9  
  3         19  
14 3     3   86 use FusionInventory::Agent::Logger;
  3         6  
  3         190  
15 3     3   16 use FusionInventory::Agent::Tools;
  3         5  
  3         603  
16 3     3   1893 use FusionInventory::Agent::HTTP::Client::Fusion;
  3         9  
  3         46  
17              
18             our $VERSION = $FusionInventory::Agent::VERSION;
19              
20             my %functions = (
21             getFromRegistry => \&_getFromRegistry,
22             findFile => \&_findFile,
23             # As decided by the FusInv-Agent developers, the runCommand function
24             # is disabled for the moment.
25             # runCommand => \&_runCommand,
26             getFromWMI => \&_getFromWMI
27             );
28              
29             # How to validate JSON for retreived jobs
30 62     62   185 sub _OPTIONAL { 0 }
31 82     82   242 sub _MANDATORY { 1 }
32 0     0   0 sub _OPTIONAL_EXCLUSIVE { 2 }
33             my %json_validation = (
34             getFromRegistry => {
35             path => _MANDATORY
36             },
37             findFile => {
38             dir => _MANDATORY,
39             limit => _MANDATORY,
40             recursive => _MANDATORY,
41             filter => {
42             regex => _OPTIONAL,
43             sizeEquals => _OPTIONAL,
44             sizeGreater => _OPTIONAL,
45             sizeLower => _OPTIONAL,
46             checkSumSHA512 => _OPTIONAL,
47             checkSumSHA2 => _OPTIONAL,
48             name => _OPTIONAL,
49             iname => _OPTIONAL,
50             is_file => _MANDATORY,
51             is_dir => _MANDATORY
52             }
53             },
54             getFromWMI => {
55             class => _MANDATORY,
56             properties => _MANDATORY
57             }
58             );
59              
60             sub isEnabled {
61 0     0 1 0 my ($self) = @_;
62              
63 0 0       0 if (!$self->{target}->isa('FusionInventory::Agent::Target::Server')) {
64 0         0 $self->{logger}->debug("ESX task not compatible with local target");
65 0         0 return;
66             }
67              
68 0         0 return 1;
69             }
70              
71             sub _validateSpec {
72 65     65   99 my ($self, $base, $key, $spec) = @_;
73              
74 65 100       130 if (ref($spec) eq 'HASH') {
75 7 100       19 if (!exists($base->{$key})) {
76 1         6 $self->{logger}->debug("$key mandatory values are missing in job");
77 0         0 return 0;
78             }
79 6         28 $self->{logger}->debug2("$key mandatory values are present in job");
80 6         8 foreach my $attribute (keys(%{$spec})) {
  6         20  
81 49 50       105 return 0 unless $self->_validateSpec($base->{$key}, $attribute, $spec->{$attribute});
82             }
83 4         16 return 1;
84             }
85              
86 58 100       101 if ($spec == _MANDATORY) {
87 20 100       43 if (!exists($base->{$key})) {
88 5         26 $self->{logger}->debug("$key mandatory value is missing in job");
89 0         0 return 0;
90             }
91 15         52 $self->{logger}->debug2("$key mandatory value is present in job");
92 15         47 return 1;
93             }
94              
95 38 50 33     57 if ($spec == _OPTIONAL && exists($base->{$key})) {
96 0         0 $self->{logger}->debug2("$key optional value is present in job");
97             }
98              
99 38         104 1;
100             }
101              
102             sub _validateAnswer {
103 14     14   21 my ($self, $answer) = @_;
104              
105 14 50       31 if (!defined($answer)) {
106 0         0 $self->{logger}->debug("Bad JSON: No answer from server.");
107 0         0 return 0;
108             }
109              
110 14 100       32 if (ref($answer) ne 'HASH') {
111 1         5 $self->{logger}->debug("Bad JSON: Bad answer from server. Not a hash reference.");
112 0         0 return 0;
113             }
114              
115 13 100 100     67 if (!defined($answer->{jobs}) || ref($answer->{jobs}) ne 'ARRAY') {
116 2         9 $self->{logger}->debug("Bad JSON: Missing jobs");
117 0         0 return 0;
118             }
119              
120 11         13 foreach my $job (@{$answer->{jobs}}) {
  11         24  
121              
122 10         20 foreach (qw/uuid function/) {
123 19 100       51 if (!defined($job->{$_})) {
124 2         11 $self->{logger}->debug("Bad JSON: Missing key '$_' in job");
125 0         0 return 0;
126             }
127             }
128              
129 8         17 my $function = $job->{function};
130 8 100       23 if (!exists($functions{$function})) {
131 1         5 $self->{logger}->debug("Bad JSON: not supported 'function' key value in job");
132 0         0 return 0;
133             }
134              
135 7 50       18 if (!exists($json_validation{$function})) {
136 0         0 $self->{logger}->debug("Bad JSON: Can't validate job");
137 0         0 return 0;
138             }
139              
140 7         8 foreach my $attribute (keys(%{$json_validation{$function}})) {
  7         21  
141 16 50       43 if (!$self->_validateSpec( $job, $attribute, $json_validation{$function}->{$attribute} )) {
142 0         0 $self->{logger}->debug("Bad JSON: '$function' job JSON format is not valid");
143 0         0 return 0;
144             }
145             }
146             }
147              
148 2         15 return 1;
149             }
150              
151             sub run {
152 20     20 1 6999 my ($self, %params) = @_;
153              
154             $self->{client} = FusionInventory::Agent::HTTP::Client::Fusion->new(
155             logger => $self->{logger},
156             user => $params{user},
157             password => $params{password},
158             proxy => $params{proxy},
159             ca_cert_file => $params{ca_cert_file},
160             ca_cert_dir => $params{ca_cert_dir},
161             no_ssl_check => $params{no_ssl_check},
162             debug => $self->{debug}
163 20         199 );
164              
165             my $globalRemoteConfig = $self->{client}->send(
166             url => $self->{target}->getUrl(),
167             args => {
168             action => "getConfig",
169             machineid => $self->{deviceid},
170 20         215 task => { Collect => $VERSION },
171             }
172             );
173              
174 19 100       353 return unless $globalRemoteConfig->{schedule};
175 18 50       57 return unless ref( $globalRemoteConfig->{schedule} ) eq 'ARRAY';
176              
177 18         22 foreach my $job ( @{ $globalRemoteConfig->{schedule} } ) {
  18         42  
178             next unless (ref($job) eq 'HASH' && exists($job->{task})
179 17 100 33     111 && $job->{task} eq "Collect");
      66        
180 16         44 $self->_processRemote($job->{remote});
181             }
182              
183 3         18 return 1;
184             }
185              
186             sub _processRemote {
187 16     16   28 my ($self, $remoteUrl) = @_;
188              
189 16 100       35 if ( !$remoteUrl ) {
190 1         4 return;
191             }
192              
193             my $answer = $self->{client}->send(
194             url => $remoteUrl,
195             args => {
196             action => "getJobs",
197             machineid => $self->{deviceid},
198             }
199 15         65 );
200              
201 15 100 100     288 if (ref($answer) eq 'HASH' && !keys %$answer) {
202 1         5 $self->{logger}->debug("Nothing to do");
203 0         0 return;
204             }
205              
206 14 50       36 return unless $self->_validateAnswer($answer);
207              
208 2 100       5 my @jobs = @{$answer->{jobs}}
  2         20  
209             or die "no jobs provided, aborting";
210              
211 1 50 33     8 my $method = exists($answer->{postmethod}) && $answer->{postmethod} eq 'POST' ? 'POST' : 'GET' ;
212 1 50       3 my $token = exists($answer->{token}) ? $answer->{token} : '';
213              
214 1         3 foreach my $job (@jobs) {
215              
216 1         10 $self->{logger}->debug2("Starting a collect job...");
217              
218 1 50       4 if ( !$job->{uuid} ) {
219 1         6 $self->{logger}->error("UUID key missing");
220 0           next;
221             }
222              
223 0           $self->{logger}->debug2("Collect job has uuid: ".$job->{uuid});
224              
225 0 0         if ( !$job->{function} ) {
226 0           $self->{logger}->error("function key missing");
227 0           next;
228             }
229              
230 0 0         if ( !defined( $functions{ $job->{function} } ) ) {
231 0           $self->{logger}->error("Bad function '$job->{function}'");
232 0           next;
233             }
234              
235 0           my @results = &{ $functions{ $job->{function} } }(%$job);
  0            
236              
237 0           my $count = int(@results);
238              
239             # Add an empty hash ref so send an answer with _cpt=0
240 0 0         push @results, {} unless $count ;
241              
242 0           foreach my $result (@results) {
243 0 0         next unless ref($result) eq 'HASH';
244 0 0 0       next unless ( !$count || keys %$result );
245 0           $result->{uuid} = $job->{uuid};
246 0           $result->{action} = "setAnswer";
247 0           $result->{_cpt} = $count;
248 0 0         $result->{_glpi_csrf_token} = $token
249             if $token ;
250             $answer = $self->{client}->send(
251             url => $remoteUrl,
252             method => $method,
253 0           filename => sprintf('collect_%s_%s.js', $job->{uuid}, $count),
254             args => $result
255             );
256 0 0         $token = exists($answer->{token}) ? $answer->{token} : '';
257 0           $count--;
258             }
259             }
260              
261 0           return $self;
262             }
263              
264             sub _encodeRegistryValueForCollect {
265 0     0     my ($value, $type) = @_ ;
266              
267             # Dump REG_BINARY/REG_RESOURCE_LIST/REG_FULL_RESOURCE_DESCRIPTOR as hex strings
268 0 0 0       if ($type == 3 || $type >= 8) {
269 0           $value = join(" ", map { sprintf "%02x", ord } split(//, $value));
  0            
270             } else {
271 0           $value = FusionInventory::Agent::Tools::Win32::encodeFromRegistry($value);
272             }
273              
274 0           return $value;
275             }
276              
277             sub _getFromRegistry {
278 0     0     my %params = @_;
279              
280 0 0         return unless FusionInventory::Agent::Tools::Win32->require();
281              
282             # Here we need to retrieve values with their type, getRegistryValue API
283             # has been modify to support withtype flag as param
284             my $values = FusionInventory::Agent::Tools::Win32::getRegistryValue(
285             path => $params{path},
286 0           withtype => 1
287             );
288              
289 0 0         return unless $values;
290              
291 0           my $result = {};
292 0 0         if (ref($values) eq 'HASH') {
293 0           foreach my $k (keys %$values) {
294             # Skip sub keys
295 0 0         next if ($k =~ m|/$|);
296 0           my ($value,$type) = @{$values->{$k}};
  0            
297 0           $result->{$k} = _encodeRegistryValueForCollect($value,$type) ;
298             }
299             } else {
300 0           my ($k) = $params{path} =~ m|([^/]+)$| ;
301 0           my ($value,$type) = @{$values};
  0            
302 0           $result->{$k} = _encodeRegistryValueForCollect($value,$type);
303             }
304              
305 0           return ($result);
306             }
307              
308             sub _findFile {
309 0     0     my %params = (
310             dir => '/',
311             limit => 50
312             , @_);
313              
314 0 0         return unless -d $params{dir};
315              
316 0           my @results;
317              
318             File::Find::find(
319             {
320             wanted => sub {
321 0 0 0 0     if (!$params{recursive} && $File::Find::name ne $params{dir}) {
322 0           $File::Find::prune = 1 # Don't recurse.
323             }
324              
325              
326 0 0 0       if ( $params{filter}{is_dir}
      0        
327             && !$params{filter}{checkSumSHA512}
328             && !$params{filter}{checkSumSHA2} )
329             {
330 0 0         return unless -d $File::Find::name;
331             }
332              
333 0 0         if ( $params{filter}{is_file} ) {
334 0 0         return unless -f $File::Find::name;
335             }
336              
337 0           my $filename = basename($File::Find::name);
338              
339 0 0         if ( $params{filter}{name} ) {
340 0 0         return if $filename ne $params{filter}{name};
341             }
342              
343 0 0         if ( $params{filter}{iname} ) {
344 0 0         return if lc($filename) ne lc( $params{filter}{iname} );
345             }
346              
347 0 0         if ( $params{filter}{regex} ) {
348 0           my $re = qr($params{filter}{regex});
349 0 0         return unless $File::Find::name =~ $re;
350             }
351              
352 0           my $st = stat($File::Find::name);
353 0           my $size = $st->size;
354 0 0         if ( $params{filter}{sizeEquals} ) {
355 0 0         return unless $size == $params{filter}{sizeEquals};
356             }
357              
358 0 0         if ( $params{filter}{sizeGreater} ) {
359 0 0         return if $size < $params{filter}{sizeGreater};
360             }
361              
362 0 0         if ( $params{filter}{sizeLower} ) {
363 0 0         return if $size > $params{filter}{sizeLower};
364             }
365              
366 0 0         if ( $params{filter}{checkSumSHA512} ) {
367 0           my $sha = Digest::SHA->new('512');
368 0           $sha->addfile( $File::Find::name, 'b' );
369             return
370 0 0         if $sha->hexdigest ne $params{filter}{checkSumSHA512};
371             }
372              
373 0 0         if ( $params{filter}{checkSumSHA2} ) {
374 0           my $sha = Digest::SHA->new('2');
375 0           $sha->addfile( $File::Find::name, 'b' );
376             return
377 0 0         if $sha->hexdigest ne $params{filter}{checkSumSHA2};
378             }
379              
380 0           push @results, {
381             size => $size,
382             path => $File::Find::name
383             };
384 0 0         goto DONE if @results >= $params{limit};
385             },
386             no_chdir => 1
387              
388             },
389             $params{dir}
390 0           );
391 0           DONE:
392              
393             return @results;
394             }
395              
396             sub _runCommand {
397 0     0     my %params = @_;
398              
399 0           my $line;
400              
401 0 0         if ( $params{filter}{firstMatch} ) {
    0          
    0          
402             $line = getFirstMatch(
403             command => $params{command},
404             pattern => $params{filter}{firstMatch}
405 0           );
406             }
407             elsif ( $params{filter}{firstLine} ) {
408 0           $line = getFirstLine( command => $params{command} );
409              
410             }
411             elsif ( $params{filter}{lineCount} ) {
412 0           $line = getLinesCount( command => $params{command} );
413             }
414             else {
415 0           $line = getAllLines( command => $params{command} );
416              
417             }
418              
419 0           return ( { output => $line } );
420             }
421              
422             sub _getFromWMI {
423 0     0     my %params = @_;
424              
425 0 0         return unless FusionInventory::Agent::Tools::Win32->require();
426              
427 0 0         return unless $params{properties};
428 0 0         return unless $params{class};
429              
430 0           my @results;
431              
432 0           my @objects = FusionInventory::Agent::Tools::Win32::getWMIObjects(%params);
433 0           foreach my $object (@objects) {
434 0           push @results, $object;
435             }
436              
437 0           return @results;
438             }
439              
440             1;