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