1###############################################################################
2## OCSINVENTORY-NG
3## Copyleft Pascal DANEK 2005
4## Web : http://ocsinventory.sourceforge.net
5##
6## This code is open source and may be copied and modified as long as the source
7## code is always made freely available.
8## Please refer to the General Public Licence http://www.gnu.org/ or Licence.txt
9################################################################################
10# Function by hook:
11# -download_prolog_reader, download_message, download
12# -download_inventory_handler
13# -download_end_handler, begin, done, clean, finish, period, download, execute,
14#   check_signature and build_package
15package Ocsinventory::Agent::Modules::Download;
16
17use strict;
18
19use Fcntl qw/:flock/;
20use XML::Simple;
21use LWP::UserAgent;
22use Compress::Zlib;
23use Digest::MD5;
24use File::Path;
25use Socket;
26
27# Can be missing. By default, we use MD5
28# You have to install it if you want to use SHA1 digest
29eval{ require Digest::SHA1 };
30
31#Global vars
32my $ua;
33my $download_config;
34my @prior_pkgs;
35
36
37sub new {
38
39    my $name="download";   #Set the name of your module here
40
41    my (undef,$context) = @_;
42    my $self = {};
43
44    # Create a special logger for the module
45    $self->{logger} = new Ocsinventory::Logger ({
46        config => $context->{config},
47    });
48
49    # We use the common object for the module
50    $self->{common} = $context->{common};
51
52    $self->{context} = $context;
53    $self->{logger}->{header}="[$name]";
54    $self->{structure} = {
55        name => $name,
56        start_handler => $name."_start_handler",
57        prolog_writer => undef,
58        prolog_reader => $name."_prolog_reader",
59        inventory_handler => $name."_inventory_handler",
60        end_handler => $name."_end_handler"
61    };
62
63    $self->{settings} = {
64        https_port => '443',
65        # Time to wait between scheduler periods, scheduling cycles and fragments downloads
66        frag_latency_default     => 10,
67        period_latency_default     => 0,
68        cycle_latency_default     => 10,
69        max_error_count            => 30,
70        # Number of loops for one period
71        period_lenght_default     => 10,
72    };
73
74    $self->{messages} = {
75        # Errors
76        code_success                     => 'SUCCESS',
77        success_already_setup                => 'SUCCESS_ALREADY_SETUP',
78        err_bad_id                     => 'ERR_BAD_ID',
79        err_download_info                 => 'ERR_DOWNLOAD_INFO',
80        err_bad_digest                     => 'ERR_BAD_DIGEST',
81        err_download_pack                 => 'ERR_DOWNLOAD_PACK',
82        err_build                     => 'ERR_BUILD',
83        err_execute                     => 'ERR_EXECUTE',
84        err_clean                     => 'ERR_CLEAN',
85        err_timeout                    => 'ERR_TIMEOUT',
86    };
87
88    # Special hash for packages
89    $self->{packages}= {};
90
91    bless $self;
92}
93
94sub download_start_handler {
95    my $self = shift;
96    my $logger = $self->{logger};
97    my $common = $self->{context}->{common};
98    my $config = $self->{context}->{config};
99
100    $logger->debug("Calling download_start_handler");
101
102    # Disabling module if local mode
103    if ($config->{stdout} || $config->{local}) {
104        $self->{disabled} = 1;
105        $logger->info("Agent is running in local mode...disabling module");
106    }
107
108    # If we cannot load prerequisite, we disable the module
109    if ($common->can_load('LWP')) {
110        my $lwp_version = $LWP::VERSION;
111        $lwp_version=$self->{common}->convertVersion($lwp_version,3);
112        if ($lwp_version > 583) {  #Newer LWP version
113            unless ($common->can_load('LWP::Protocol::https')) {
114            $self->{disabled} = 1;
115            $logger->error("LWP::Protocol::https perl module is missing !!");
116            $logger->error("Humm my prerequisites are not OK...disabling module :( :(");
117            }
118        } else {
119            unless ($common->can_load('Crypt::SSLeay')) {
120            $self->{disabled} = 1;
121            $logger->error("Crypt::SSLeay perl module is missing !!");
122            $logger->error("Humm my prerequisites are not OK...disabling module :( :(");
123            }
124        }
125    } else {
126        $self->{disabled} = 1;
127        $logger->error("LWP perl module is missing !!");
128        $logger->error("Humm my prerequisites are not OK...disabling module :( :(");
129    }
130}
131
132sub download_prolog_reader{      #Read prolog response
133
134    my ($self,$prolog) = @_;
135    my $context = $self->{context};
136    my $logger = $self->{logger};
137    my $config = $self->{context}->{config};
138    my $network = $self->{context}->{network};
139    my $common = $self->{common};
140    my $settings = $self->{settings};
141    my $messages = $self->{messages};
142    my $packages = $self->{packages};
143
144    $logger->debug("Calling download_prolog_reader");
145    $logger->debug($prolog);
146    $prolog = XML::Simple::XMLin( $prolog, ForceArray => ['OPTION', 'PARAM']);
147    my $option;
148    # Create working directory
149    my $opt_dir = $context->{installpath}.'/download';
150    mkdir($opt_dir) unless -d $opt_dir;
151
152    # We create a file to tell to download process that we are running
153    open SUSPEND, ">$opt_dir/suspend";
154    close(SUSPEND);
155
156    # Create history file if needed
157    unless(-e "$opt_dir/history"){
158        open HISTORY, ">$opt_dir/history" or die("Cannot create history file: $!");
159        close(HISTORY);
160    }
161
162    # Create lock file if needed
163    unless(-e "$opt_dir/lock"){
164        open LOCK, ">$opt_dir/lock" or die("Cannot create lock file: $!");
165        close(LOCK);
166    }
167
168    # Retrieve our options
169    for $option (@{$prolog->{OPTION}}){
170        if ($option->{NAME} =~/download/i){
171            for (@{ $option->{PARAM} } ) {
172                # Type of param
173                if ($_->{'TYPE'} eq 'CONF'){
174                    # Writing configuration
175                    open CONFIG, ">$opt_dir/config" or die("Cannot open/create config file ($opt_dir/config)");
176                    if (flock(CONFIG, LOCK_EX)){
177                        $logger->debug("Writing config file.");
178                        print CONFIG XMLout($_, RootName => 'CONF');
179                        close(CONFIG);
180                        $download_config = $_;
181                    } else {
182                        $logger->error("Cannot lock config file !!");
183                        close(CONFIG);
184                        return 0;
185                    }
186
187                    # Apply config
188                    # ON ?
189                    if ($_->{'ON'} == '0'){
190                        $logger->info("Download is off.");
191                        open LOCK, "$opt_dir/lock" or die("Cannot open lock file: $!");
192                        if (flock(LOCK, LOCK_EX|LOCK_NB)){
193                            close(LOCK);
194                            unlink("$opt_dir/suspend");
195                            return 0;
196                        } else {
197                            $logger->debug("Try to kill current download process...");
198                            my $pid = <LOCK>;
199                            close(LOCK);
200                            $logger->debug("Sending USR1 to $pid...");
201                            if (kill("USR1", $pid)){
202                                $logger->debug("Success.");
203                            } else {
204                                $logger->debug("Failed.");
205                            }
206                            return 0;
207                        }
208                    }
209                    # Maybe a new package to download
210                } elsif ($_->{'TYPE'} eq 'PACK'){
211                    $packages->{$_->{'ID'}} = {
212                        'PACK_LOC' => $_->{'PACK_LOC'},
213                        'INFO_LOC' => $_->{'INFO_LOC'},
214                        #'ID' => $_->{'ID'},
215                        'CERT_PATH' => $_->{'CERT_PATH'},
216                        'CERT_FILE' => $_->{'CERT_FILE'},
217                        'FORCE' => $_->{'FORCE'}
218                    };
219                }
220            }
221        }
222    }
223    # We are now in download child
224    # Connect to server
225    if ($context->{network}) {
226        $ua = $context->{network}->{ua};
227    } else {
228        $logger->info("Cannot find network settings to make this module works properly...disabling module");
229        $self->{disabled} = 1;
230    }
231
232    # Check history file
233    unless(open HISTORY, "$opt_dir/history") {
234        flock(HISTORY, LOCK_EX);
235        unlink("$opt_dir/suspend");
236        $logger->error("Cannot read history file: $!");
237        return 1;
238    }
239
240    chomp(my @done = <HISTORY>);
241    close(HISTORY);
242
243    # Package is maybe already handled
244    for (keys %$packages){
245        my $dir = $opt_dir."/".$_;
246        my $fileid = $_;
247        my $infofile = 'info';
248        my $location = $packages->{$_}->{'INFO_LOC'};
249
250        unless ($packages->{$_}->{'FORCE'} == 1) {
251            if($common->already_in_array($fileid, @done)){
252                $logger->info("Will not download $fileid. (already in history file)");
253                &download_message($fileid, $messages->{success_already_setup},$logger,$context);
254                next;
255            }
256        }
257
258        # Looking for packages status
259        unless(-d $dir){
260            $logger->debug("Making working directory for $fileid.");
261            mkdir($dir) or die("Cannot create $fileid directory: $!");
262            open FH, ">$dir/since" or die("Cannot create $fileid since file: $!");;
263            print FH time();
264            close(FH);
265        }
266
267        # Retrieve and writing info file if needed
268        unless(-f "$dir/$infofile"){
269            # Special value INSTALL_PATH
270            $packages->{$_}->{CERT_PATH} =~ s/INSTALL_PATH/$context->{installpath}/;
271            $packages->{$_}->{CERT_FILE} =~ s/INSTALL_PATH/$context->{installpath}/;
272
273            # Getting info file
274            if ($network->getFile("https","$location/$fileid","info","$dir/info")){
275                download_message($fileid, $self->{messages}->{err_download_info},$logger,$context);
276                $logger->error("Error download info file !!! Wrong URL or SSL certificate ?");
277                next;
278            }
279        }
280    }
281    unless(unlink("$opt_dir/suspend")){
282        $logger->error("Cannot delete suspend file: $!");
283        return 1;
284    }
285    return 0;
286}
287
288sub ssl_verify_callback {
289    my ($ok, $x509_store_ctx) = @_;
290    return $ok;
291}
292
293sub download_inventory_handler{          # Adding the ocs package ids to softwares
294    my ($self,$inventory) = @_;
295    my $context = $self->{context};
296    my $logger = $self->{logger};
297
298    $logger->debug("Calling download_inventory_handler");
299
300    my @history;
301
302    # Read download history file
303    if ( open PACKAGES, "$context->{installpath}/download/history" ){
304        flock(PACKAGES, LOCK_SH);
305        while(<PACKAGES>){
306            chomp( $_ );
307            push @history, { ID => $_ };
308        }
309    }
310    close(PACKAGES);
311
312    # Add it to inventory (will be handled by Download.pm server module
313    push @{ $inventory->{xmlroot}->{'CONTENT'}->{'DOWNLOAD'}->{'HISTORY'} },{
314        'PACKAGE'=> \@history
315    };
316}
317
318sub download_end_handler{        # Get global structure
319
320    my $self = shift;
321    my $context = $self->{context};
322    my $logger = $self->{logger};
323    my $common = $self->{common};
324    my $settings = $self->{settings};
325    my $messages = $self->{messages};
326    my $packages = $self->{packages};
327
328    $logger->debug("Calling download_end_handler");
329
330    my $dir = $context->{installpath}."/download";
331    my $pidfile = $dir."/lock";
332
333    return 0 unless -d $dir;
334
335    # We have jobs, we do it alone
336    my $fork = fork();
337    if ($fork>0){
338        return 0;
339    } elsif ($fork<0){
340        return 1;
341    } else {
342        $SIG{'USR1'} = sub {
343            print "Exiting on signal...\n";
344            &finish($logger, $context);
345        };
346        # Go into working directory
347        chdir($dir) or die("Cannot chdir to working directory...Abort\n");
348    }
349
350    # Maybe an other process is running
351    exit(0) if begin($pidfile,$logger);
352    # Retrieve the packages to download
353    opendir DIR, $dir or die("Cannot read working directory: $!");
354
355    my $end;
356
357    while(1){
358        # If agent is running, we wait
359        if (-e "suspend") {
360            $logger->debug('Found a suspend file... Will wait 10 seconds before retry');
361            sleep(10);
362            next;
363        }
364
365        $end = 1;
366
367        #TODO Uncomment this line #undef $packages;
368
369        # Reading configuration
370        open FH, "$dir/config";
371        if (flock(FH, LOCK_SH)){
372            $download_config = XMLin("$dir/config");
373            close(FH);
374            # If Frag latency is null, download is off
375            if ($download_config->{'ON'} eq '0'){
376                $logger->info("Option turned off. Exiting.");
377                finish($logger, $context);
378            }
379        } else {
380            close(FH);
381            if (-e "$dir/config") {
382                $logger->error("Cannot read config file :-( . Exiting.");
383            } else {
384                $logger->debug("Download not configured");
385            }
386            finish($logger, $context);
387        }
388
389        # Retrieving packages to download and their priority
390        while (my $entry = readdir(DIR)){
391            next if $entry !~ /^\d+$/;
392            next unless(-d $entry);
393
394            # Clean package if info file does not still exist
395            unless(-e "$entry/info"){
396                $logger->debug("No info file found for $entry!!");
397                clean( $entry, $logger, $context, $messages, $packages );
398                next;
399            }
400            my $info = XML::Simple::XMLin( "$entry/info" ) or next;
401
402            # Check that fileid == directory name
403            if ($info->{'ID'} ne $entry){
404                $logger->debug("ID in info file does not correspond!!");
405                clean( $entry, $logger, $context, $messages, $packages );
406                download_message($entry, $messages->{err_bad_id},$logger,$context);
407                next;
408            }
409
410            # Manage package timeout
411            # Clean package if since timestamp is not present
412            unless(-e "$entry/since"){
413                $logger->debug("No since file found!!");
414                clean($entry, $logger, $context,$messages,$packages );
415                next;
416            } else {
417                my $time = time();
418                if (open SINCE, "$entry/since"){
419                    my $since = <SINCE>;
420                    if ($since=~/\d+/){
421                        if ((($time-$since)/86400) > $download_config->{TIMEOUT}){
422                            $logger->error("Timeout Reached for $entry.");
423                            clean($entry, $logger, $context,$messages,$packages );
424                            &download_message($entry, $messages->{err_timeout},$logger,$context);
425                            close(SINCE);
426                            next;
427                        } else {
428                            $logger->debug("Checking timeout for $entry... OK");
429                        }
430                    } else {
431                        $logger->error("Since data for $entry is incorrect.");
432                        clean($entry, $logger, $context, $messages, $packages );
433                        &download_message($entry, $messages->{err_timeout},$logger,$context);
434                        close(SINCE);
435                        next;
436                    }
437                    close(SINCE);
438                } else {
439                    $logger->error("Cannot find since data for $entry.");
440                    clean($entry, $logger, $context, $messages, $packages );
441                    &download_message($entry, $messages->{err_timeout},$logger,$context);
442                    next;
443                }
444            }
445
446            # Building task file if needed
447            unless( -f "$entry/task" and -f "$entry/task_done" ){
448                open FH, ">$entry/task" or die("Cannot create task file for $entry: $!");
449
450                my $i;
451                my $frags = $info->{'FRAGS'};
452                # There are no frags if there is only a command
453                if ($frags){
454                    for ($i=1;$i<=$frags;$i++){
455                        print FH "$entry-$i\n";
456                    }
457                };
458                close FH;
459                # To be sure that task file is fully created
460                open FLAG, ">$entry/task_done" or die ("Cannot create task flag file for $entry: $!");
461                close(FLAG);
462            }
463            # Store info XML descriptions in package attributes
464            for (keys %$info){
465                $packages->{$entry}->{$_} = $info->{$_}
466            }
467            $end = 0;
468        }
469        # Rewind directory
470        rewinddir(DIR);
471        # Call packages scheduler
472        if ($end){
473            last;
474        } else {
475            period($packages,$logger,$context,$self->{messages},$settings);
476        }
477    }
478    $logger->info("No more package to download.");
479    finish($logger, $context);
480}
481
482# Schedule the packages
483sub period{
484    my ($packages,$logger,$context,$messages,$settings) = @_ ;
485
486    my $period_lenght_default = $settings->{period_lenght_default} ;
487    my $frag_latency_default= $settings->{frag_latency_default} ;
488    my $cycle_latency_default= $settings->{cycle_latency_default} ;
489    my $period_latency_default= $settings->{period_latency_default} ;
490
491    my $i;
492
493    #Serching packages with the priority 0
494    for (keys %$packages) {
495        if ($packages->{$_}->{'PRI'} eq "0") {
496            push (@prior_pkgs,$_);
497        }
498    }
499
500    $logger->debug("New period. Nb of cycles: ".
501    (defined($download_config->{'PERIOD_LENGTH'})?$download_config->{'PERIOD_LENGTH'}:$period_lenght_default));
502
503    for ($i=1;$i<=( defined($download_config->{'PERIOD_LENGTH'})?$download_config->{'PERIOD_LENGTH'}:$period_lenght_default);$i++){
504        # Highest priority
505        if (@prior_pkgs){
506            $logger->debug("Managing ".scalar(@prior_pkgs)." package(s) with absolute priority.");
507            for (@prior_pkgs){
508                # If done file found, clean package
509                if (-e "$_/done"){
510                    $logger->debug("done file found!!");
511                    done($_,$logger,$context,$messages,$settings,$packages);
512                    next;
513                }
514                download($_,$logger,$context,$messages,$settings,$packages);
515                $logger->debug("Now pausing for a fragment latency => ".(
516                    defined($download_config->{'FRAG_LATENCY'})?$download_config->{'FRAG_LATENCY'}:$frag_latency_default)
517                    ." seconds");
518                sleep( defined($download_config->{'FRAG_LATENCY'})?$download_config->{'FRAG_LATENCY'}:$frag_latency_default );
519            }
520            next;
521        }
522
523        # Normal priority
524
525        for (keys %$packages){
526            # If done file found, clean package
527            if(-e "$_/done"){
528                $logger->debug("done file found!!");
529                done($_,$logger,$context,$messages,$settings,$packages);
530                next;
531            }
532            next if $i % $packages->{$_}->{'PRI'} != 0;
533            download($_,$logger,$context,$messages,$settings,$packages);
534
535            $logger->debug("Now pausing for a fragment latency => ".
536            (defined( $download_config->{'FRAG_LATENCY'} )?$download_config->{'FRAG_LATENCY'}:$frag_latency_default)
537            ." seconds");
538
539            sleep(defined($download_config->{'FRAG_LATENCY'})?$download_config->{'FRAG_LATENCY'}:$frag_latency_default);
540        }
541
542        $logger->debug("Now pausing for a cycle latency => ".(
543        defined($download_config->{'CYCLE_LATENCY'})?$download_config->{'CYCLE_LATENCY'}:$cycle_latency_default)
544        ." seconds");
545
546        sleep(defined($download_config->{'CYCLE_LATENCY'})?$download_config->{'CYCLE_LATENCY'}:$cycle_latency_default);
547    }
548    sleep($download_config->{'PERIOD_LATENCY'}?$download_config->{'PERIOD_LATENCY'}:$period_latency_default);
549}
550
551# Download a fragment of the specified package
552sub download {
553    my ($id,$logger,$context,$messages,$settings,$packages) = @_;
554
555    my $error;
556    my $proto = $packages->{$id}->{'PROTO'};
557    my $location = $packages->{$id}->{'PACK_LOC'};
558    my $network = $context->{network};
559
560    # If we find a temp file, we know that the update of the task file has failed for any reason. So we retrieve it from this file
561    if (-e "$id/task.temp") {
562        unlink("$id/task.temp");
563        rename("$id/task.temp","$id/task") or return 1;
564    }
565
566    # Retrieve fragments already downloaded
567    unless(open TASK, "$id/task"){
568        $logger->error("Cannot open $id/task.");
569        return 1;
570    }
571    my @task = <TASK>;
572
573    # Done
574    if (!@task){
575        $logger->debug("Download of $id... Finished.");
576        close(TASK);
577        execute($id,$logger,$context,$messages,$settings,$packages);
578        return 0;
579    }
580
581    my $fragment = shift(@task);
582
583    $logger->debug("Downloading $fragment...");
584
585    # Using proxy if possible
586    my $res = $network->getFile(lc($proto),"$location/$id",$fragment,"$id/$fragment");
587
588    # Checking if connected
589    unless($res) {
590        #Success
591        $error = 0;
592
593        # Updating task file
594        rename(">$id/task", ">$id/task.temp");
595        open TASK, ">$id/task" or return 1;
596        print TASK @task;
597        close(TASK);
598        unlink(">$id/task.temp");
599    } else {
600        $error++;
601        if ($error > $settings->{max_error_count}){
602            $logger->error("Error : Max errors count reached");
603            finish($logger,$context);
604        }
605        return 1;
606    }
607    return 0;
608}
609
610# Assemble and handle downloaded package
611sub execute{
612    my ($id,$logger,$context,$messages,$settings,$packages) = @_;
613
614    my $common = $context->{common};
615
616    my $tmp = $id."/tmp";
617    my $exit_code;
618
619    $logger->debug("Execute orders for package $id.");
620
621    if (build_package($id,$logger,$context,$messages,$packages)){
622        clean($id,$logger, $context,$messages,$packages);
623        return 1;
624    } else {
625        # First, we get in temp directory
626        unless( chdir($tmp) ){
627            $logger->error("Cannot chdir to working directory: $!");
628            download_message($id, $messages->{err_execute}, $logger,$context);
629            clean($id,$logger, $context,$messages,$packages);
630            return 1;
631        }
632
633        # Executing preorders (notify user, auto launch, etc....
634        # $id->{NOTIFY_USER}
635        # $id->{NOTIFY_TEXT}
636        # $id->{NOTIFY_COUNTDOWN}
637        # $id->{NOTIFY_CAN_ABORT}
638        # TODO: notification to send through DBUS to the user
639
640        eval{
641            # Execute instructions
642            if ($packages->{$id}->{'ACT'} eq 'LAUNCH'){
643                my $exe_line = $packages->{$id}->{'NAME'};
644                $packages->{$id}->{'NAME'} =~ s/^([^ -]+).*/$1/;
645                # Exec specified file (LAUNCH => NAME)
646                if (-e $packages->{$id}->{'NAME'}){
647                    $logger->debug("Launching $packages->{$id}->{'NAME'}...");
648                    chmod(0755, $packages->{$id}->{'NAME'}) or die("Cannot chmod: $!");
649                    $exit_code = system( "./".$exe_line ) >> 8;
650                } else {
651                    die();
652                }
653
654            } elsif ($packages->{$id}->{'ACT'} eq 'EXECUTE'){
655                # Exec specified command EXECUTE => COMMAND
656                $logger->debug("Execute $packages->{$id}->{'COMMAND'}...");
657                system( $packages->{$id}->{'COMMAND'} ) and die();
658
659            } elsif ($packages->{$id}->{'ACT'} eq 'STORE'){
660                # Store files in specified path STORE => PATH
661                $packages->{$id}->{'PATH'} =~ s/INSTALL_PATH/$context->{installpath}/;
662
663                # Build it if needed
664                my @dir = split('/', $packages->{$id}->{'PATH'});
665                my $dir;
666
667                for (@dir){
668                    $dir .= "$_/";
669                    unless(-e $dir){
670                        mkdir($dir);
671                        $logger->debug("Create $dir...");
672                    }
673                }
674
675                $logger->debug("Storing package to $packages->{$id}->{'PATH'}...");
676                # Stefano Brandimarte => Stevenson! <stevens@stevens.it>
677                system($common->get_path('cp')." -pr * ".$packages->{$id}->{'PATH'}) and die();
678            }
679        };
680        if ($@){
681            # Notify success to ocs server
682            download_message($id, $messages->{err_execute},$logger,$context);
683            chdir("../..") or die("Cannot go back to download directory: $!");
684            clean($id,$logger,$context,$messages,$packages);
685            return 1;
686        } else {
687            chdir("../..") or die("Cannot go back to download directory: $!");
688            done($id,$logger,$context,$messages,$settings,$packages,(defined($exit_code)?$exit_code:'_NONE_'));
689            return 0;
690        }
691    }
692}
693
694# Check package integrity
695sub build_package{
696    my ($id,$logger,$context,$messages,$packages) = @_;
697
698    my $common = $context->{common};
699
700    my $count = $packages->{$id}->{'FRAGS'};
701    my $i;
702    my $tmp = "./$id/tmp";
703
704    unless(-d $tmp){
705        mkdir("$tmp");
706    }
707    # No job if no files
708    return 0 unless $count;
709
710    # Assemble package
711    $logger->info("Building package for $id.");
712
713    for ($i=1;$i<=$count;$i++){
714        if (-f "./$id/$id-$i"){
715            # We make a tmp working directory
716            if ($i==1){
717                open PACKAGE, ">$tmp/build.tar.gz" or return 1;
718            }
719            # We write each fragment in the final package
720            open FRAGMENT, "./$id/$id-$i" or return 1;
721            my $row;
722            while ($row = <FRAGMENT>){
723                print PACKAGE $row;
724            }
725            close(FRAGMENT);
726        } else {
727            return 1;
728        }
729    }
730    close(PACKAGE);
731    #
732    if (check_signature($packages->{$id}->{'DIGEST'}, "$tmp/build.tar.gz", $packages->{$id}->{'DIGEST_ALGO'}, $packages->{$id}->{'DIGEST_ENCODE'},$logger)){
733        download_message($id, $messages->{err_bad_digest},$logger,$context);
734        return 1;
735    }
736
737    if ( system( $common->get_path("tar")." -xvzf $tmp/build.tar.gz -C $tmp") ){
738        $logger->error("Cannot extract $id with tar, trying with unzip.");
739        if ( system( $common->get_path("unzip")." $tmp/build.tar.gz -d $tmp") ){
740            $logger->error("Cannot extract $id with unzip.");
741            download_message($id,$messages->{err_build},$logger,$context);
742            return 1;
743        }
744    }
745    $logger->debug("Building of $id... Success.");
746    unlink("$tmp/build.tar.gz") or die ("Cannot remove build file: $!\n");
747    return 0;
748}
749
750sub check_signature{
751    my ($checksum, $file, $digest, $encode,$logger) = @_;
752
753    $logger->info("Checking signature for $file.");
754
755    my $base64;
756
757    # Open file
758    unless(open FILE, $file){
759        $logger->error("cannot open $file: $!");
760        return 1;
761    }
762
763    binmode(FILE);
764    # Retrieving encoding form
765    if ($encode =~ /base64/i){
766        $base64 = 1;
767        $logger->debug('Digest format: Base 64');
768    } elsif ($encode =~ /hexa/i){
769        $logger->debug('Digest format: Hexadecimal');
770    } else {
771        $logger->debug('Digest format: Not supported');
772        return 1;
773    }
774
775    eval{
776        # Check it
777        if ($digest eq 'MD5'){
778            $logger->debug('Digest algo: MD5');
779            if ($base64){
780                die unless Digest::MD5->new->addfile(*FILE)->b64digest eq $checksum;
781            } else {
782                die unless Digest::MD5->new->addfile(*FILE)->hexdigest eq $checksum;
783            }
784        } elsif ($digest eq 'SHA1'){
785            $logger->debug('Digest algo: SHA1');
786            if ($base64){
787                die unless Digest::SHA1->new->addfile(*FILE)->b64digest eq $checksum;
788            } else {
789                die unless Digest::SHA1->new->addfile(*FILE)->hexdigest eq $checksum;
790            }
791        } else {
792            $logger->debug('Digest algo unknown: '.$digest);
793            die;
794        }
795    };
796    if ($@){
797        $logger->debug("Digest checking error !!");
798        close(FILE);
799        return 1;
800    } else {
801        close(FILE);
802        $logger->debug("Digest OK...");
803        return 0;
804    }
805}
806
807# Launch a download error to ocs server
808sub download_message{
809    my ($id, $code,$logger,$context) = @_;
810
811    $logger->debug("Sending message for $id, code=$code.");
812
813    my $xml = {
814        'DEVICEID' => $context->{deviceid},
815        'QUERY' => 'DOWNLOAD',
816        'ID' => $id,
817        'ERR' => $code
818    };
819
820    # Generate xml
821    $xml = XMLout($xml, RootName => 'REQUEST');
822
823    # Compress data
824    $xml = Compress::Zlib::compress( $xml );
825
826    my $URI = $context->{servername};
827
828    # Send request
829    my $request = HTTP::Request->new(POST => $URI);
830    $request->header('Pragma' => 'no-cache', 'Content-type', 'application/x-compress');
831    $request->content($xml);
832    my $res = $ua->request($request);
833
834    # Checking result
835    if ($res->is_success) {
836        return 0;
837    }else{
838        return 1;
839    }
840}
841
842# At the beginning of end handler
843sub begin{
844    my ($pidfile,$logger) = @_;
845
846    open LOCK_R, "$pidfile" or die("Cannot open pid file: $!");
847    if (flock(LOCK_R,LOCK_EX|LOCK_NB)){
848        open LOCK_W, ">$pidfile" or die("Cannot open pid file: $!");
849        select(LOCK_W) and $|=1;
850        select(STDOUT) and $|=1;
851        print LOCK_W $$;
852        $logger->info("Beginning work. I am $$.");
853        return 0;
854    } else {
855        close(LOCK_R);
856        $logger->error("$pidfile locked. Cannot begin work... :-(");
857        return 1;
858    }
859}
860
861sub done{
862    my ($id,$logger,$context,$messages,$settings,$packages,$suffix) = @_;
863
864    my $common = $context->{common};
865
866    my $frag_latency_default = $settings->{frag_latency_default};
867
868    $logger->debug("Package $id... Done. Sending message...");
869    # Trace installed package
870    open DONE, ">$id/done";
871    close(DONE);
872
873    # Read history file
874    open HISTORY,"$context->{installpath}/download/history" or warn("Cannot open history file: $!");
875    chomp(my @historyIds = <HISTORY>);
876    close(HISTORY);
877
878    # Put it in history file
879    open HISTORY,">>$context->{installpath}/download/history" or warn("Cannot open history file: $!");
880    flock(HISTORY, LOCK_EX);
881
882    if ( $common->already_in_array($id, @historyIds) ){
883        $logger->debug("Warning: ID $id has been found in the history file (package was already deployed)");
884    } else {
885        $logger->debug("Writing $id reference in history file");
886        print HISTORY $id,"\n";
887    }
888    close(HISTORY);
889
890    # Notify success to ocs server
891    my $code;
892    if ($suffix ne '_NONE_'){
893        $code = $messages->{code_success}."_$suffix";
894    } else {
895        $code = $messages->{code_success};
896    }
897    unless(download_message($id, $code,$logger,$context)){
898        clean($id,$logger,$context,$messages,$packages);
899    } else {
900        sleep( defined($download_config->{'FRAG_LATENCY'})?$download_config->{'FRAG_LATENCY'}:$frag_latency_default );
901    }
902    return 0;
903}
904
905sub clean{
906    my ($id,$logger,$context,$messages,$packages) = @_;
907
908    $logger->info("Cleaning $id package.");
909
910    delete $packages->{$id};
911
912    #If the package is priority 0
913    if ((my $index) = grep { $prior_pkgs[$_] eq $id } 0..$#prior_pkgs){
914        delete $prior_pkgs[$index];
915    }
916
917    unless(File::Path::rmtree($id, 0)){
918        $logger->error("Cannot clean $id!! Abort...");
919        download_message($id, $messages->{err_clean},$logger,$context);
920        die();
921    }
922    return 0;
923}
924
925# At the end
926sub finish{
927    my ($logger,$context) = @_;
928
929    open LOCK, '>'.$context->{installpath}.'/download/lock';
930    $logger->debug("End of work...\n");
931    exit(0);
932}
933
9341;
935