1#************************************************************************* 2# 3# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4# 5# Copyright 2000, 2010 Oracle and/or its affiliates. 6# 7# OpenOffice.org - a multi-platform office productivity suite 8# 9# This file is part of OpenOffice.org. 10# 11# OpenOffice.org is free software: you can redistribute it and/or modify 12# it under the terms of the GNU Lesser General Public License version 3 13# only, as published by the Free Software Foundation. 14# 15# OpenOffice.org is distributed in the hope that it will be useful, 16# but WITHOUT ANY WARRANTY; without even the implied warranty of 17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18# GNU Lesser General Public License version 3 for more details 19# (a copy is included in the LICENSE file that accompanied this code). 20# 21# You should have received a copy of the GNU Lesser General Public License 22# version 3 along with OpenOffice.org. If not, see 23# <http://www.openoffice.org/license.html> 24# for a copy of the LGPLv3 License. 25# 26#************************************************************************* 27 28package installer::windows::sign; 29 30use Cwd; 31use installer::converter; 32use installer::existence; 33use installer::files; 34use installer::globals; 35use installer::scriptitems; 36use installer::worker; 37use installer::windows::admin; 38 39######################################################## 40# Copying an existing Windows installation set. 41######################################################## 42 43sub copy_install_set 44{ 45 my ( $installsetpath ) = @_; 46 47 installer::logger::include_header_into_logfile("Start: Copying installation set $installsetpath"); 48 49 my $infoline = ""; 50 51 my $dirname = $installsetpath; 52 installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$dirname); 53 54 my $path = $installsetpath; 55 installer::pathanalyzer::get_path_from_fullqualifiedname(\$path); 56 57 $path =~ s/\Q$installer::globals::separator\E\s*$//; 58 59 if ( $dirname =~ /\./ ) { $dirname =~ s/\./_signed_inprogress./; } 60 else { $dirname = $dirname . "_signed_inprogress"; } 61 62 my $newpath = $path . $installer::globals::separator . $dirname; 63 my $removepath = $newpath; 64 $removepath =~ s/_inprogress/_witherror/; 65 66 if ( -d $newpath ) { installer::systemactions::remove_complete_directory($newpath, 1); } 67 if ( -d $removepath ) { installer::systemactions::remove_complete_directory($removepath, 1); } 68 69 $infoline = "Copy installation set from $installsetpath to $newpath\n"; 70 push( @installer::globals::logfileinfo, $infoline); 71 72 $installsetpath = installer::systemactions::copy_complete_directory($installsetpath, $newpath); 73 74 installer::logger::include_header_into_logfile("End: Copying installation set $installsetpath"); 75 76 return $newpath; 77} 78 79######################################################## 80# Renaming an existing Windows installation set. 81######################################################## 82 83sub rename_install_set 84{ 85 my ( $installsetpath ) = @_; 86 87 my $infoline = ""; 88 89 my $dirname = $installsetpath; 90 installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$dirname); 91 92 my $path = $installsetpath; 93 installer::pathanalyzer::get_path_from_fullqualifiedname(\$path); 94 95 $path =~ s/\Q$installer::globals::separator\E\s*$//; 96 97 if ( $dirname =~ /\./ ) { $dirname =~ s/\./_inprogress./; } 98 else { $dirname = $dirname . "_inprogress"; } 99 100 my $newpath = $path . $installer::globals::separator . $dirname; 101 my $removepath = $newpath; 102 $removepath =~ s/_inprogress/_witherror/; 103 104 if ( -d $newpath ) { installer::systemactions::remove_complete_directory($newpath, 1); } 105 if ( -d $removepath ) { installer::systemactions::remove_complete_directory($removepath, 1); } 106 107 $installsetpath = installer::systemactions::rename_directory($installsetpath, $newpath); 108 109 return $newpath; 110} 111 112######################################################### 113# Checking the local system 114# Checking existence of needed files in include path 115######################################################### 116 117sub check_system_path 118{ 119 # The following files have to be found in the environment variable PATH 120 # Only, if \"-sign\" is used. 121 # Windows : "msicert.exe", "diff.exe", "msidb.exe", "signtool.exe" 122 123 my @needed_files_in_path = ("msicert.exe", "msidb.exe", "signtool.exe", "diff.exe"); 124 if ( $installer::globals::internal_cabinet_signing ) 125 { 126 push(@needed_files_in_path, "cabarc.exe"); 127 push(@needed_files_in_path, "makecab.exe"); 128 } 129 130 my $onefile; 131 my $error = 0; 132 my $pathvariable = $ENV{'PATH'}; 133 my $local_pathseparator = $installer::globals::pathseparator; 134 135 if( $^O =~ /cygwin/i ) 136 { # When using cygwin's perl the PATH variable is POSIX style and ... 137 $pathvariable = qx{cygpath -mp "$pathvariable"} ; 138 # has to be converted to DOS style for further use. 139 $local_pathseparator = ';'; 140 } 141 142 my $patharrayref = installer::converter::convert_stringlist_into_array(\$pathvariable, $local_pathseparator); 143 144 $installer::globals::patharray = $patharrayref; 145 146 foreach my $onefile ( @needed_files_in_path ) 147 { 148 installer::logger::print_message( "...... searching $onefile ..." ); 149 150 my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath_classic(\$onefile, $patharrayref , 0); 151 152 if ( $$fileref eq "" ) 153 { 154 $error = 1; 155 installer::logger::print_error( "$onefile not found\n" ); 156 } 157 else 158 { 159 installer::logger::print_message( "\tFound: $$fileref\n" ); 160 } 161 } 162 163 $installer::globals::signfiles_checked = 1; 164 165 if ( $error ) { installer::exiter::exit_program("ERROR: Could not find all needed files in path!", "check_system_path"); } 166} 167 168###################################################### 169# Making systemcall 170###################################################### 171 172sub make_systemcall 173{ 174 my ($systemcall, $displaysystemcall) = @_; 175 176 installer::logger::print_message( "... $displaysystemcall ...\n" ); 177 178 my $success = 1; 179 my $returnvalue = system($systemcall); 180 181 my $infoline = "Systemcall: $displaysystemcall\n"; 182 push( @installer::globals::logfileinfo, $infoline); 183 184 if ($returnvalue) 185 { 186 $infoline = "ERROR: Could not execute \"$displaysystemcall\"!\n"; 187 push( @installer::globals::logfileinfo, $infoline); 188 $success = 0; 189 } 190 else 191 { 192 $infoline = "Success: Executed \"$displaysystemcall\" successfully!\n"; 193 push( @installer::globals::logfileinfo, $infoline); 194 } 195 196 return $success; 197} 198 199###################################################### 200# Making systemcall with warning 201###################################################### 202 203sub make_systemcall_with_warning 204{ 205 my ($systemcall, $displaysystemcall) = @_; 206 207 installer::logger::print_message( "... $displaysystemcall ...\n" ); 208 209 my $success = 1; 210 my $returnvalue = system($systemcall); 211 212 my $infoline = "Systemcall: $displaysystemcall\n"; 213 push( @installer::globals::logfileinfo, $infoline); 214 215 if ($returnvalue) 216 { 217 $infoline = "WARNING: Could not execute \"$displaysystemcall\"!\n"; 218 push( @installer::globals::logfileinfo, $infoline); 219 $success = 0; 220 } 221 else 222 { 223 $infoline = "Success: Executed \"$displaysystemcall\" successfully!\n"; 224 push( @installer::globals::logfileinfo, $infoline); 225 } 226 227 return $success; 228} 229 230###################################################### 231# Making systemcall with more return data 232###################################################### 233 234sub execute_open_system_call 235{ 236 my ( $systemcall ) = @_; 237 238 my @openoutput = (); 239 my $success = 1; 240 241 my $comspec = $ENV{COMSPEC}; 242 $comspec = $comspec . " -c "; 243 244 if( $^O =~ /cygwin/i ) 245 { 246 # $comspec =~ s/\\/\\\\/g; 247 # $comspec = qx{cygpath -u "$comspec"}; 248 # $comspec =~ s/\s*$//g; 249 $comspec = ""; 250 } 251 252 my $localsystemcall = "$comspec $systemcall 2>&1 |"; 253 254 open( OPN, "$localsystemcall") or warn "Can't execute $localsystemcall\n"; 255 while (<OPN>) { push(@openoutput, $_); } 256 close (OPN); 257 258 my $returnvalue = $?; # $? contains the return value of the systemcall 259 260 if ($returnvalue) 261 { 262 $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; 263 push( @installer::globals::logfileinfo, $infoline); 264 $success = 0; 265 } 266 else 267 { 268 $infoline = "Success: Executed \"$systemcall\" successfully!\n"; 269 push( @installer::globals::logfileinfo, $infoline); 270 } 271 272 return ($success, \@openoutput); 273} 274 275######################################################## 276# Reading first line of pw file. 277######################################################## 278 279sub get_pw 280{ 281 my ( $file ) = @_; 282 283 my $filecontent = installer::files::read_file($file); 284 285 my $pw = ${$filecontent}[0]; 286 $pw =~ s/^\s*//; 287 $pw =~ s/\s*$//; 288 289 return $pw; 290} 291 292######################################################## 293# Counting the keys of a hash. 294######################################################## 295 296sub get_hash_count 297{ 298 my ($hashref) = @_; 299 300 my $counter = 0; 301 302 foreach my $key ( keys %{$hashref} ) { $counter++; } 303 304 return $counter; 305} 306 307############################################################ 308# Collect all last files in a cabinet file. This is 309# necessary to control, if the cabinet file was damaged 310# by calling signtool.exe. 311############################################################ 312 313sub analyze_file_file 314{ 315 my ($filecontent) = @_; 316 317 my %filenamehash = (); 318 319 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) 320 { 321 if ( $i < 3 ) { next; } 322 323 if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) 324 { 325 my $name = $1; 326 my $sequence = $8; 327 328 $filenamehash{$sequence} = $name; 329 } 330 } 331 332 return ( \%filenamehash ); 333} 334 335############################################################ 336# Collect all DiskIds to the corresponding cabinet files. 337############################################################ 338 339sub analyze_media_file 340{ 341 my ($filecontent) = @_; 342 343 my %diskidhash = (); 344 my %lastsequencehash = (); 345 346 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) 347 { 348 if ( $i < 3 ) { next; } 349 350 if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) 351 { 352 my $diskid = $1; 353 my $lastsequence = $2; 354 my $cabfile = $4; 355 356 $diskidhash{$cabfile} = $diskid; 357 $lastsequencehash{$cabfile} = $lastsequence; 358 } 359 } 360 361 return ( \%diskidhash, \%lastsequencehash ); 362} 363 364######################################################## 365# Collect all DiskIds from database table "Media". 366######################################################## 367 368sub collect_diskid_from_media_table 369{ 370 my ($msidatabase, $languagestring) = @_; 371 372 # creating working directory 373 my $workdir = installer::systemactions::create_directories("media", \$languagestring); 374 installer::windows::admin::extract_tables_from_pcpfile($msidatabase, $workdir, "Media File"); 375 376 # Reading tables 377 my $filename = $workdir . $installer::globals::separator . "Media.idt"; 378 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find required file: $filename !", "collect_diskid_from_media_table"); } 379 my $filecontent = installer::files::read_file($filename); 380 my ( $diskidhash, $lastsequencehash ) = analyze_media_file($filecontent); 381 382 $filename = $workdir . $installer::globals::separator . "File.idt"; 383 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find required file: $filename !", "collect_diskid_from_media_table"); } 384 $filecontent = installer::files::read_file($filename); 385 my $filenamehash = analyze_file_file($filecontent); 386 387 return ( $diskidhash, $filenamehash, $lastsequencehash ); 388} 389 390######################################################## 391# Check, if this installation set contains 392# internal cabinet files included into the msi 393# database. 394######################################################## 395 396sub check_for_internal_cabfiles 397{ 398 my ($cabfilehash) = @_; 399 400 my $contains_internal_cabfiles = 0; 401 my %allcabfileshash = (); 402 403 foreach my $filename ( keys %{$cabfilehash} ) 404 { 405 if ( $filename =~ /^\s*\#/ ) # starting with a hash 406 { 407 $contains_internal_cabfiles = 1; 408 # setting real filename without hash as key and name with hash as value 409 my $realfilename = $filename; 410 $realfilename =~ s/^\s*\#//; 411 $allcabfileshash{$realfilename} = $filename; 412 } 413 } 414 415 return ( $contains_internal_cabfiles, \%allcabfileshash ); 416} 417 418######################################################## 419# Collecting all files in an installation set. 420######################################################## 421 422sub analyze_installset_content 423{ 424 my ( $installsetpath ) = @_; 425 426 my @sourcefiles = (); 427 my $pathstring = ""; 428 installer::systemactions::read_complete_directory($installsetpath, $pathstring, \@sourcefiles); 429 430 if ( ! ( $#sourcefiles > -1 )) { installer::exiter::exit_program("ERROR: No file in installation set. Path: $installsetpath !", "analyze_installset_content"); } 431 432 my %allcabfileshash = (); 433 my %allmsidatabaseshash = (); 434 my %allfileshash = (); 435 my $contains_external_cabfiles = 0; 436 my $msidatabase = ""; 437 my $contains_msidatabase = 0; 438 439 for ( my $j = 0; $j <= $#sourcefiles; $j++ ) 440 { 441 if ( $sourcefiles[$j] =~ /\.cab\s*$/ ) { $allcabfileshash{$sourcefiles[$j]} = 1; } 442 else 443 { 444 if ( $sourcefiles[$j] =~ /\.txt\s*$/ ) { next; } 445 if ( $sourcefiles[$j] =~ /\.html\s*$/ ) { next; } 446 if ( $sourcefiles[$j] =~ /\.ini\s*$/ ) { next; } 447 if ( $sourcefiles[$j] =~ /\.bmp\s*$/ ) { next; } 448 if ( $sourcefiles[$j] =~ /\.msi\s*$/ ) 449 { 450 if ( $msidatabase eq "" ) { $msidatabase = $sourcefiles[$j]; } 451 else { installer::exiter::exit_program("ERROR: There is more than one msi database in installation set. Path: $installsetpath !", "analyze_installset_content"); } 452 } 453 $allfileshash{$sourcefiles[$j]} = 1; 454 } 455 } 456 457 # Is there at least one cab file in the installation set? 458 my $cabcounter = get_hash_count(\%allcabfileshash); 459 if ( $cabcounter > 0 ) { $contains_external_cabfiles = 1; } 460 461 # How about a cab file without a msi database? 462 if (( $cabcounter > 0 ) && ( $msidatabase eq "" )) { installer::exiter::exit_program("ERROR: There is no msi database in the installation set, but an external cabinet file. Path: $installsetpath !", "collect_installset_content"); } 463 464 if ( $msidatabase ne "" ) { $contains_msidatabase = 1; } 465 466 return (\%allcabfileshash, \%allfileshash, $msidatabase, $contains_external_cabfiles, $contains_msidatabase, \@sourcefiles); 467} 468 469######################################################## 470# Adding content of external cabinet files into the 471# msi database 472######################################################## 473 474sub msicert_database 475{ 476 my ($msidatabase, $allcabfiles, $cabfilehash, $internalcabfile) = @_; 477 478 my $fullsuccess = 1; 479 480 foreach my $cabfile ( keys %{$allcabfiles} ) 481 { 482 my $origfilesize = -s $cabfile; 483 484 my $mediacabfilename = $cabfile; 485 if ( $internalcabfile ) { $mediacabfilename = "\#" . $mediacabfilename; } 486 if ( ! exists($cabfilehash->{$mediacabfilename}) ) { installer::exiter::exit_program("ERROR: Could not determine DiskId from media table for cabinet file \"$cabfile\" !", "msicert_database"); } 487 my $diskid = $cabfilehash->{$mediacabfilename}; 488 489 my $systemcall = "msicert.exe -d $msidatabase -m $diskid -c $cabfile -h"; 490 $success = make_systemcall($systemcall, $systemcall); 491 if ( ! $success ) { $fullsuccess = 0; } 492 493 # size of cabinet file must not change 494 my $finalfilesize = -s $cabfile; 495 496 if ( $origfilesize != $finalfilesize ) { installer::exiter::exit_program("ERROR: msicert.exe changed size of cabinet file !", "msicert_database"); } 497 } 498 499 return $fullsuccess; 500} 501 502######################################################## 503# Checking if cabinet file was broken by signtool. 504######################################################## 505 506sub cabinet_cosistency_check 507{ 508 my ( $onefile, $followmeinfohash, $filenamehash, $lastsequencehash, $temppath ) = @_; 509 510 my $infoline = "Making consistency check of $onefile\n"; 511 push( @installer::globals::logfileinfo, $infoline); 512 my $expandfile = "expand.exe"; # Has to be in the path 513 514 if ( $^O =~ /cygwin/i ) 515 { 516 $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe); 517 chomp $expandfile; 518 } 519 520 if ( $filenamehash == 0 ) 521 { 522 $infoline = "Warning: Stopping consistency check: Important hash of filenames is empty!\n"; 523 push( @installer::globals::logfileinfo, $infoline); 524 } 525 elsif ( $lastsequencehash == 0 ) 526 { 527 $infoline = "Warning: Stopping consistency check; Important hash of last sequences is empty!\n"; 528 push( @installer::globals::logfileinfo, $infoline); 529 } 530 else # both hashes are available 531 { 532 # $onefile contains only the name of the cabinet file without path 533 my $sequence = $lastsequencehash->{$onefile}; 534 my $lastfile = $filenamehash->{$sequence}; 535 $infoline = "Check of $onefile: Sequence: $sequence is file: $lastfile\n"; 536 push( @installer::globals::logfileinfo, $infoline); 537 538 # Therefore the file $lastfile need to be binary compared. 539 # It has to be expanded from the cabinet file 540 # of the original installation set and from the 541 # newly signed cabinet file. 542 543 # How about cabinet files extracted from msi database? 544 my $finalinstalldir = $followmeinfohash->{'finalinstalldir'}; 545 546 $finalinstalldir =~ s/\\\s*$//; 547 $finalinstalldir =~ s/\/\s*$//; 548 my $sourcecabfile = $finalinstalldir . $installer::globals::separator . $onefile; 549 my $currentpath = cwd(); 550 my $destcabfile = $currentpath . $installer::globals::separator . $onefile; 551 # my $destcabfile = $onefile; 552 553 if ( $^O =~ /cygwin/i ) 554 { 555 chomp( $destcabfile = qx{cygpath -w "$destcabfile"} ); 556 $destcabfile =~ s/\\/\//g; 557 } 558 559 if ( ! -f $sourcecabfile ) 560 { 561 $infoline = "WARNING: Check of cab file cannot happen, because source cabinet file was not found: $sourcecabfile\n"; 562 push( @installer::globals::logfileinfo, $infoline); 563 } 564 elsif ( ! -f $destcabfile ) 565 { 566 $infoline = "WARNING: Check of cab file cannot happen, because destination cabinet file was not found: $sourcecabfile\n"; 567 push( @installer::globals::logfileinfo, $infoline); 568 } 569 else # everything is okay for the check 570 { 571 my $diffpath = get_diff_path($temppath); 572 573 my $origdiffpath = $diffpath . $installer::globals::separator . "orig"; 574 my $newdiffpath = $diffpath . $installer::globals::separator . "new"; 575 576 if ( ! -d $origdiffpath ) { mkdir($origdiffpath); } 577 if ( ! -d $newdiffpath ) { mkdir($newdiffpath); } 578 579 my $systemcall = "$expandfile $sourcecabfile $origdiffpath -f:$lastfile "; 580 $infoline = $systemcall . "\n"; 581 push( @installer::globals::logfileinfo, $infoline); 582 583 my $success = make_systemcall($systemcall, $systemcall); 584 if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not successfully execute: $systemcall !", "cabinet_cosistency_check"); } 585 586 $systemcall = "$expandfile $destcabfile $newdiffpath -f:$lastfile "; 587 $infoline = $systemcall . "\n"; 588 push( @installer::globals::logfileinfo, $infoline); 589 590 $success = make_systemcall($systemcall, $systemcall); 591 if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not successfully execute: $systemcall !", "cabinet_cosistency_check"); } 592 593 # and finally the two files can be diffed. 594 my $origfile = $origdiffpath . $installer::globals::separator . $lastfile; 595 my $newfile = $newdiffpath . $installer::globals::separator . $lastfile; 596 597 if ( ! -f $origfile ) { installer::exiter::exit_program("ERROR: Unpacked original file not found: $origfile !", "cabinet_cosistency_check"); } 598 if ( ! -f $newfile ) { installer::exiter::exit_program("ERROR: Unpacked new file not found: $newfile !", "cabinet_cosistency_check"); } 599 600 my $origsize = -s $origfile; 601 my $newsize = -s $newfile; 602 603 if ( $origsize != $newsize ) # This shows an error! 604 { 605 $infoline = "ERROR: Different filesize after signtool.exe was used. Original: $origsize Bytes, new: $newsize. File: $lastfile\n"; 606 push( @installer::globals::logfileinfo, $infoline); 607 installer::exiter::exit_program("ERROR: The cabinet file $destcabfile is broken after signtool.exe signed this file !", "cabinet_cosistency_check"); 608 } 609 else 610 { 611 $infoline = "Same size of last file in cabinet file after usage of signtool.exe: $newsize (File: $lastfile)\n"; 612 push( @installer::globals::logfileinfo, $infoline); 613 614 # Also making a binary diff? 615 616 my $difffile = "diff.exe"; # has to be in the path 617 # $systemcall = "$difffile $sourcecabfile $destcabfile"; # Test for differences 618 $systemcall = "$difffile $origfile $newfile"; 619 $infoline = $systemcall . "\n"; 620 $returnvalue = make_systemcall($systemcall, $systemcall); 621 622 my $success = $?; 623 624 if ( $success == 0 ) 625 { 626 $infoline = "Last files are identical after signing cabinet file (File: $lastfile)\n"; 627 push( @installer::globals::logfileinfo, $infoline); 628 } 629 elsif ( $success == 1 ) 630 { 631 $infoline = "ERROR: Last files are different after signing cabinet file (File: $lastfile)\n"; 632 push( @installer::globals::logfileinfo, $infoline); 633 installer::exiter::exit_program("ERROR: Last files are different after signing cabinet file (File: $lastfile)!", "cabinet_cosistency_check"); 634 } 635 else 636 { 637 $infoline = "ERROR: Problem occured calling diff.exe (File: $lastfile)\n"; 638 push( @installer::globals::logfileinfo, $infoline); 639 installer::exiter::exit_program("ERROR: Problem occured calling diff.exe (File: $lastfile) !", "cabinet_cosistency_check"); 640 } 641 } 642 } 643 } 644 645} 646 647######################################################## 648# Signing a list of files 649######################################################## 650 651sub sign_files 652{ 653 my ( $followmeinfohash, $allfiles, $pw, $cabinternal, $filenamehash, $lastsequencehash, $temppath ) = @_; 654 655 my $infoline = ""; 656 my $fullsuccess = 1; 657 my $maxcounter = 3; 658 659 my $productname = ""; 660 if ( $followmeinfohash->{'allvariableshash'}->{'PRODUCTNAME'} ) { $productname = "/d " . "\"$followmeinfohash->{'allvariableshash'}->{'PRODUCTNAME'}\""; } 661 my $url = ""; 662 if (( ! exists($followmeinfohash->{'allvariableshash'}->{'OPENSOURCE'}) ) || ( $followmeinfohash->{'allvariableshash'}->{'OPENSOURCE'} == 0 )) { $url = "/du " . "\"http://www.sun.com\""; } 663 else { $url = "/du " . "\"http://www.openoffice.org\""; } 664 my $timestampurl = "http://timestamp.verisign.com/scripts/timestamp.dll"; 665 666 my $pfxfilepath = $installer::globals::pfxfile; 667 668 if( $^O =~ /cygwin/i ) 669 { 670 $pfxfilepath = qx{cygpath -w "$pfxfilepath"}; 671 $pfxfilepath =~ s/\\/\\\\/g; 672 $pfxfilepath =~ s/\s*$//g; 673 } 674 675 foreach my $onefile ( reverse sort keys %{$allfiles} ) 676 { 677 if ( already_certified($onefile) ) 678 { 679 $infoline = "Already certified: Skipping file $onefile\n"; 680 push( @installer::globals::logfileinfo, $infoline); 681 next; 682 } 683 684 my $counter = 1; 685 my $success = 0; 686 687 while (( $counter <= $maxcounter ) && ( ! $success )) 688 { 689 if ( $counter > 1 ) { installer::logger::print_message( "\n\n... repeating file $onefile ...\n" ); } 690 if ( $cabinternal ) { installer::logger::print_message(" Signing: $onefile\n"); } 691 my $systemcall = "signtool.exe sign /f \"$pfxfilepath\" /p $pw $productname $url /t \"$timestampurl\" \"$onefile\""; 692 my $displaysystemcall = "signtool.exe sign /f \"$pfxfilepath\" /p ***** $productname $url /t \"$timestampurl\" \"$onefile\""; 693 $success = make_systemcall_with_warning($systemcall, $displaysystemcall); 694 $counter++; 695 } 696 697 # Special check for cabinet files, that sometimes get damaged by signtool.exe 698 if (( $success ) && ( $onefile =~ /\.cab\s*$/ ) && ( ! $cabinternal )) 699 { 700 cabinet_cosistency_check($onefile, $followmeinfohash, $filenamehash, $lastsequencehash, $temppath); 701 } 702 703 if ( ! $success ) 704 { 705 $fullsuccess = 0; 706 installer::exiter::exit_program("ERROR: Could not sign file: $onefile!", "sign_files"); 707 } 708 } 709 710 return $fullsuccess; 711} 712 713########################################################################## 714# Lines in ddf files must not contain more than 256 characters 715########################################################################## 716 717sub check_ddf_file 718{ 719 my ( $ddffile, $ddffilename ) = @_; 720 721 my $maxlength = 0; 722 my $maxline = 0; 723 my $linelength = 0; 724 my $linenumber = 0; 725 726 for ( my $i = 0; $i <= $#{$ddffile}; $i++ ) 727 { 728 my $oneline = ${$ddffile}[$i]; 729 730 $linelength = length($oneline); 731 $linenumber = $i + 1; 732 733 if ( $linelength > 256 ) 734 { 735 installer::exiter::exit_program("ERROR \"$ddffilename\" line $linenumber: Lines in ddf files must not contain more than 256 characters!", "check_ddf_file"); 736 } 737 738 if ( $linelength > $maxlength ) 739 { 740 $maxlength = $linelength; 741 $maxline = $linenumber; 742 } 743 } 744 745 my $infoline = "Check of ddf file \"$ddffilename\": Maximum length \"$maxlength\" in line \"$maxline\" (allowed line length: 256 characters)\n"; 746 push( @installer::globals::logfileinfo, $infoline); 747} 748 749################################################################# 750# Setting the path, where the cab files are unpacked. 751################################################################# 752 753sub get_cab_path 754{ 755 my ($temppath) = @_; 756 757 my $cabpath = "cabs_" . $$; 758 $cabpath = $temppath . $installer::globals::separator . $cabpath; 759 if ( ! -d $cabpath ) { installer::systemactions::create_directory($cabpath); } 760 761 return $cabpath; 762} 763 764################################################################# 765# Setting the path, where the diff can happen. 766################################################################# 767 768sub get_diff_path 769{ 770 my ($temppath) = @_; 771 772 my $diffpath = "diff_" . $$; 773 $diffpath = $temppath . $installer::globals::separator . $diffpath; 774 if ( ! -d $diffpath ) { installer::systemactions::create_directory($diffpath); } 775 776 return $diffpath; 777} 778 779################################################################# 780# Exclude all cab files from the msi database. 781################################################################# 782 783sub extract_cabs_from_database 784{ 785 my ($msidatabase, $allcabfiles) = @_; 786 787 installer::logger::include_header_into_logfile("Extracting cabs from msi database"); 788 789 my $infoline = ""; 790 my $fullsuccess = 1; 791 my $msidb = "msidb.exe"; # Has to be in the path 792 793 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) 794 $msidatabase =~ s/\//\\\\/g; 795 796 foreach my $onefile ( keys %{$allcabfiles} ) 797 { 798 my $systemcall = $msidb . " -d " . $msidatabase . " -x " . $onefile; 799 my $success = make_systemcall($systemcall, $systemcall); 800 if ( ! $success ) { $fullsuccess = 0; } 801 802 # and removing the stream from the database 803 $systemcall = $msidb . " -d " . $msidatabase . " -k " . $onefile; 804 $success = make_systemcall($systemcall, $systemcall); 805 if ( ! $success ) { $fullsuccess = 0; } 806 } 807 808 return $fullsuccess; 809} 810 811################################################################# 812# Include cab files into the msi database. 813################################################################# 814 815sub include_cabs_into_database 816{ 817 my ($msidatabase, $allcabfiles) = @_; 818 819 installer::logger::include_header_into_logfile("Including cabs into msi database"); 820 821 my $infoline = ""; 822 my $fullsuccess = 1; 823 my $msidb = "msidb.exe"; # Has to be in the path 824 825 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) 826 $msidatabase =~ s/\//\\\\/g; 827 828 foreach my $onefile ( keys %{$allcabfiles} ) 829 { 830 my $systemcall = $msidb . " -d " . $msidatabase . " -a " . $onefile; 831 my $success = make_systemcall($systemcall, $systemcall); 832 if ( ! $success ) { $fullsuccess = 0; } 833 } 834 835 return $fullsuccess; 836} 837 838######################################################## 839# Reading the order of the files inside the 840# cabinet files. 841######################################################## 842 843sub read_cab_file 844{ 845 my ($cabfilename) = @_; 846 847 installer::logger::print_message( "\n... reading cabinet file $cabfilename ...\n" ); 848 my $infoline = "Reading cabinet file $cabfilename\n"; 849 push( @installer::globals::logfileinfo, $infoline); 850 851 my $systemcall = "cabarc.exe" . " L " . $cabfilename; 852 push(@logfile, "$systemcall\n"); 853 854 my ($success, $fileorder) = execute_open_system_call($systemcall); 855 856 my @allfiles = (); 857 858 for ( my $i = 0; $i <= $#{$fileorder}; $i++ ) 859 { 860 my $line = ${$fileorder}[$i]; 861 if ( $line =~ /^\s*(.*?)\s+\d+\s+\d+\/\d+\/\d+\s+\d+\:\d+\:\d+\s+[\w-]+\s*$/ ) 862 { 863 my $filename = $1; 864 push(@allfiles, $filename); 865 } 866 } 867 868 return \@allfiles; 869} 870 871######################################################## 872# Unpacking a cabinet file. 873######################################################## 874 875sub unpack_cab_file 876{ 877 my ($cabfilename, $temppath) = @_; 878 879 installer::logger::print_message( "\n... unpacking cabinet file $cabfilename ...\n" ); 880 my $infoline = "Unpacking cabinet file $cabfilename\n"; 881 push( @installer::globals::logfileinfo, $infoline); 882 883 my $dirname = $cabfilename; 884 $dirname =~ s/\.cab\s*$//; 885 my $workingpath = $temppath . $installer::globals::separator . "unpack_". $dirname . "_" . $$; 886 if ( ! -d $workingpath ) { installer::systemactions::create_directory($workingpath); } 887 888 # changing into unpack directory 889 my $from = cwd(); 890 chdir($workingpath); 891 892 my $fullcabfilename = $from . $installer::globals::separator . $cabfilename; 893 894 if( $^O =~ /cygwin/i ) 895 { 896 $fullcabfilename = qx{cygpath -w "$fullcabfilename"}; 897 $fullcabfilename =~ s/\\/\\\\/g; 898 $fullcabfilename =~ s/\s*$//g; 899 } 900 901 my $systemcall = "cabarc.exe" . " -p X " . $fullcabfilename; 902 $success = make_systemcall($systemcall, $systemcall); 903 if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not unpack cabinet file: $fullcabfilename!", "unpack_cab_file"); } 904 905 # returning to directory 906 chdir($from); 907 908 return $workingpath; 909} 910 911######################################################## 912# Returning the header of a ddf file. 913######################################################## 914 915sub get_ddf_file_header 916{ 917 my ($ddffileref, $cabinetfile, $installdir) = @_; 918 919 my $oneline; 920 my $compressionlevel = 2; 921 922 if( $^O =~ /cygwin/i ) 923 { 924 $installdir = qx{cygpath -w "$installdir"}; 925 $installdir =~ s/\s*$//g; 926 } 927 928 $oneline = ".Set CabinetName1=" . $cabinetfile . "\n"; 929 push(@{$ddffileref} ,$oneline); 930 $oneline = ".Set ReservePerCabinetSize=128\n"; # This reserves space for a digital signature. 931 push(@{$ddffileref} ,$oneline); 932 $oneline = ".Set MaxDiskSize=2147483648\n"; # This allows the .cab file to get a size of 2 GB. 933 push(@{$ddffileref} ,$oneline); 934 $oneline = ".Set CompressionType=LZX\n"; 935 push(@{$ddffileref} ,$oneline); 936 $oneline = ".Set Compress=ON\n"; 937 push(@{$ddffileref} ,$oneline); 938 $oneline = ".Set CompressionLevel=$compressionlevel\n"; 939 push(@{$ddffileref} ,$oneline); 940 $oneline = ".Set Cabinet=ON\n"; 941 push(@{$ddffileref} ,$oneline); 942 $oneline = ".Set DiskDirectoryTemplate=" . $installdir . "\n"; 943 push(@{$ddffileref} ,$oneline); 944} 945 946######################################################## 947# Writing content into ddf file. 948######################################################## 949 950sub put_all_files_into_ddffile 951{ 952 my ($ddffile, $allfiles, $workingpath) = @_; 953 954 $workingpath =~ s/\//\\/g; 955 956 for ( my $i = 0; $i <= $#{$allfiles}; $i++ ) 957 { 958 my $filename = ${$allfiles}[$i]; 959 if( $^O =~ /cygwin/i ) { $filename =~ s/\//\\/g; } # Backslash for Cygwin! 960 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file: $filename!", "put_all_files_into_ddffile"); } 961 my $infoline = "\"" . $filename . "\"" . " " . ${$allfiles}[$i] . "\n"; 962 push( @{$ddffile}, $infoline); 963 } 964} 965 966######################################################## 967# Packing a cabinet file. 968######################################################## 969 970sub do_pack_cab_file 971{ 972 my ($cabfilename, $allfiles, $workingpath, $temppath) = @_; 973 974 installer::logger::print_message( "\n... packing cabinet file $cabfilename ...\n" ); 975 my $infoline = "Packing cabinet file $cabfilename\n"; 976 push( @installer::globals::logfileinfo, $infoline); 977 978 if ( -f $cabfilename ) { unlink($cabfilename); } # removing cab file 979 if ( -f $cabfilename ) { installer::exiter::exit_program("ERROR: Failed to remove file: $cabfilename!", "do_pack_cab_file"); } 980 981 # generate ddf file for makecab.exe 982 my @ddffile = (); 983 984 my $dirname = $cabfilename; 985 $dirname =~ s/\.cab\s*$//; 986 my $ddfpath = $temppath . $installer::globals::separator . "ddf_". $dirname . "_" . $$; 987 988 my $ddffilename = $cabfilename; 989 $ddffilename =~ s/.cab/.ddf/; 990 $ddffilename = $ddfpath . $installer::globals::separator . $ddffilename; 991 992 if ( ! -d $ddfpath ) { installer::systemactions::create_directory($ddfpath); } 993 994 my $from = cwd(); 995 996 chdir($workingpath); # changing into the directory with the unpacked files 997 998 get_ddf_file_header(\@ddffile, $cabfilename, $from); 999 put_all_files_into_ddffile(\@ddffile, $allfiles, $workingpath); 1000 # lines in ddf files must not be longer than 256 characters 1001 check_ddf_file(\@ddffile, $ddffilename); 1002 1003 installer::files::save_file($ddffilename, \@ddffile); 1004 1005 if( $^O =~ /cygwin/i ) 1006 { 1007 $ddffilename = qx{cygpath -w "$ddffilename"}; 1008 $ddffilename =~ s/\\/\\\\/g; 1009 $ddffilename =~ s/\s*$//g; 1010 } 1011 1012 my $systemcall = "makecab.exe /V1 /F " . $ddffilename; 1013 my $success = make_systemcall($systemcall, $systemcall); 1014 if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not pack cabinet file!", "do_pack_cab_file"); } 1015 1016 chdir($from); 1017 1018 return ($success); 1019} 1020 1021######################################################## 1022# Extraction the file extension from a file 1023######################################################## 1024 1025sub get_extension 1026{ 1027 my ( $file ) = @_; 1028 1029 my $extension = ""; 1030 1031 if ( $file =~ /^\s*(.*)\.(\w+?)\s*$/ ) { $extension = $2; } 1032 1033 return $extension; 1034} 1035 1036######################################################## 1037# Checking, if a file already contains a certificate. 1038# This must not be overwritten. 1039######################################################## 1040 1041sub already_certified 1042{ 1043 my ( $filename ) = @_; 1044 1045 my $success = 1; 1046 my $is_certified = 0; 1047 1048 my $systemcall = "signtool.exe verify /q /pa \"$filename\""; 1049 my $returnvalue = system($systemcall); 1050 1051 if ( $returnvalue ) { $success = 0; } 1052 1053 # my $success = make_systemcall($systemcall, $systemcall); 1054 1055 if ( $success ) 1056 { 1057 $is_certified = 1; 1058 installer::logger::print_message( "... already certified -> skipping $filename ...\n" ); 1059 } 1060 1061 return $is_certified; 1062} 1063 1064######################################################## 1065# Signing the files, that are included into 1066# cabinet files. 1067######################################################## 1068 1069sub sign_files_in_cabinet_files 1070{ 1071 my ( $followmeinfohash, $allcabfiles, $pw, $temppath ) = @_; 1072 1073 my $complete_success = 1; 1074 my $from = cwd(); 1075 1076 foreach my $cabfilename ( keys %{$allcabfiles} ) 1077 { 1078 my $success = 1; 1079 1080 # saving order of files in cab file 1081 my $fileorder = read_cab_file($cabfilename); 1082 1083 # unpack into $working path 1084 my $workingpath = unpack_cab_file($cabfilename, $temppath); 1085 1086 chdir($workingpath); 1087 1088 # sign files 1089 my %allfileshash = (); 1090 foreach my $onefile ( @{$fileorder} ) 1091 { 1092 my $extension = get_extension($onefile); 1093 if ( exists( $installer::globals::sign_extensions{$extension} ) ) 1094 { 1095 $allfileshash{$onefile} = 1; 1096 } 1097 } 1098 $success = sign_files($followmeinfohash, \%allfileshash, $pw, 1, 0, 0, $temppath); 1099 if ( ! $success ) { $complete_success = 0; } 1100 1101 chdir($from); 1102 1103 # pack into new directory 1104 do_pack_cab_file($cabfilename, $fileorder, $workingpath, $temppath); 1105 } 1106 1107 return $complete_success; 1108} 1109 1110######################################################## 1111# Comparing the content of two directories. 1112# Only filesize is compared. 1113######################################################## 1114 1115sub compare_directories 1116{ 1117 my ( $dir1, $dir2, $files ) = @_; 1118 1119 $dir1 =~ s/\\\s*//; 1120 $dir2 =~ s/\\\s*//; 1121 $dir1 =~ s/\/\s*//; 1122 $dir2 =~ s/\/\s*//; 1123 1124 my $infoline = "Comparing directories: $dir1 and $dir2\n"; 1125 push( @installer::globals::logfileinfo, $infoline); 1126 1127 foreach my $onefile ( @{$files} ) 1128 { 1129 my $file1 = $dir1 . $installer::globals::separator . $onefile; 1130 my $file2 = $dir2 . $installer::globals::separator . $onefile; 1131 1132 if ( ! -f $file1 ) { installer::exiter::exit_program("ERROR: Missing file : $file1!", "compare_directories"); } 1133 if ( ! -f $file2 ) { installer::exiter::exit_program("ERROR: Missing file : $file2!", "compare_directories"); } 1134 1135 my $size1 = -s $file1; 1136 my $size2 = -s $file2; 1137 1138 $infoline = "Comparing files: $file1 ($size1) and $file2 ($size2)\n"; 1139 push( @installer::globals::logfileinfo, $infoline); 1140 1141 if ( $size1 != $size2 ) 1142 { 1143 installer::exiter::exit_program("ERROR: File defect after copy (different size) $file1 ($size1 bytes) and $file2 ($size2 bytes)!", "compare_directories"); 1144 } 1145 } 1146} 1147 1148######################################################## 1149# Signing an existing Windows installation set. 1150######################################################## 1151 1152sub sign_install_set 1153{ 1154 my ($followmeinfohash, $make_copy, $temppath) = @_; 1155 1156 my $installsetpath = $followmeinfohash->{'finalinstalldir'}; 1157 1158 installer::logger::include_header_into_logfile("Start: Signing installation set $installsetpath"); 1159 1160 my $complete_success = 1; 1161 my $success = 1; 1162 1163 my $infoline = "Signing installation set in $installsetpath\n"; 1164 push( @installer::globals::logfileinfo, $infoline); 1165 1166 # check required files. 1167 if ( ! $installer::globals::signfiles_checked ) { check_system_path(); } 1168 1169 # get cerficate information 1170 my $pw = get_pw($installer::globals::pwfile); 1171 1172 # making a copy of the installation set, if required 1173 if ( $make_copy ) { $installsetpath = copy_install_set($installsetpath); } 1174 else { $installsetpath = rename_install_set($installsetpath); } 1175 1176 # collecting all files in the installation set 1177 my ($allcabfiles, $allfiles, $msidatabase, $contains_external_cabfiles, $contains_msidatabase, $sourcefiles) = analyze_installset_content($installsetpath); 1178 1179 if ( $make_copy ) { compare_directories($installsetpath, $followmeinfohash->{'finalinstalldir'}, $sourcefiles); } 1180 1181 # changing into installation set 1182 my $from = cwd(); 1183 my $fullmsidatabase = $installsetpath . $installer::globals::separator . $msidatabase; 1184 1185 if( $^O =~ /cygwin/i ) 1186 { 1187 $fullmsidatabase = qx{cygpath -w "$fullmsidatabase"}; 1188 $fullmsidatabase =~ s/\\/\\\\/g; 1189 $fullmsidatabase =~ s/\s*$//g; 1190 } 1191 1192 chdir($installsetpath); 1193 1194 if ( $contains_msidatabase ) 1195 { 1196 # exclude media table from msi database and get all diskids. 1197 my ( $cabfilehash, $filenamehash, $lastsequencehash ) = collect_diskid_from_media_table($msidatabase, $followmeinfohash->{'languagestring'}); 1198 1199 # Check, if there are internal cab files 1200 my ( $contains_internal_cabfiles, $all_internal_cab_files) = check_for_internal_cabfiles($cabfilehash); 1201 1202 if ( $contains_internal_cabfiles ) 1203 { 1204 my $cabpath = get_cab_path($temppath); 1205 chdir($cabpath); 1206 1207 # Exclude all cabinet files from database 1208 $success = extract_cabs_from_database($fullmsidatabase, $all_internal_cab_files); 1209 if ( ! $success ) { $complete_success = 0; } 1210 1211 if ( $installer::globals::internal_cabinet_signing ) { sign_files_in_cabinet_files($followmeinfohash, $all_internal_cab_files, $pw, $temppath); } 1212 1213 $success = sign_files($followmeinfohash, $all_internal_cab_files, $pw, 0, $filenamehash, $lastsequencehash, $temppath); 1214 if ( ! $success ) { $complete_success = 0; } 1215 $success = msicert_database($fullmsidatabase, $all_internal_cab_files, $cabfilehash, 1); 1216 if ( ! $success ) { $complete_success = 0; } 1217 1218 # Include all cabinet files into database 1219 $success = include_cabs_into_database($fullmsidatabase, $all_internal_cab_files); 1220 if ( ! $success ) { $complete_success = 0; } 1221 chdir($installsetpath); 1222 } 1223 1224 # Warning: There might be a problem with very big cabinet files 1225 # signing all external cab files first 1226 if ( $contains_external_cabfiles ) 1227 { 1228 if ( $installer::globals::internal_cabinet_signing ) { sign_files_in_cabinet_files($followmeinfohash, $allcabfiles, $pw, $temppath); } 1229 1230 $success = sign_files($followmeinfohash, $allcabfiles, $pw, 0, $filenamehash, $lastsequencehash, $temppath); 1231 if ( ! $success ) { $complete_success = 0; } 1232 $success = msicert_database($msidatabase, $allcabfiles, $cabfilehash, 0); 1233 if ( ! $success ) { $complete_success = 0; } 1234 } 1235 } 1236 1237 # finally all other files can be signed 1238 $success = sign_files($followmeinfohash, $allfiles, $pw, 0, 0, 0, $temppath); 1239 if ( ! $success ) { $complete_success = 0; } 1240 1241 # and changing back 1242 chdir($from); 1243 1244 installer::logger::include_header_into_logfile("End: Signing installation set $installsetpath"); 1245 1246 return ($installsetpath); 1247} 1248 12491; 1250