File Coverage

blib/lib/FusionInventory/Agent/Task/Collect.pm
Criterion Covered Total %
statement 99 223 44.3
branch 36 134 26.8
condition 11 36 30.5
subroutine 18 26 69.2
pod 2 2 100.0
total 166 421 39.4


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