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