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