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