#************************************************************** # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # #************************************************************** package installer::windows::sign; use Cwd; use installer::converter; use installer::existence; use installer::files; use installer::globals; use installer::scriptitems; use installer::worker; use installer::windows::admin; ######################################################## # Copying an existing Windows installation set. ######################################################## sub copy_install_set { my ( $installsetpath ) = @_; installer::logger::include_header_into_logfile("Start: Copying installation set $installsetpath"); my $infoline = ""; my $dirname = $installsetpath; installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$dirname); my $path = $installsetpath; installer::pathanalyzer::get_path_from_fullqualifiedname(\$path); $path =~ s/\Q$installer::globals::separator\E\s*$//; if ( $dirname =~ /\./ ) { $dirname =~ s/\./_signed_inprogress./; } else { $dirname = $dirname . "_signed_inprogress"; } my $newpath = $path . $installer::globals::separator . $dirname; my $removepath = $newpath; $removepath =~ s/_inprogress/_witherror/; if ( -d $newpath ) { installer::systemactions::remove_complete_directory($newpath, 1); } if ( -d $removepath ) { installer::systemactions::remove_complete_directory($removepath, 1); } $infoline = "Copy installation set from $installsetpath to $newpath\n"; $installer::logger::Lang->print($infoline); $installsetpath = installer::systemactions::copy_complete_directory($installsetpath, $newpath); installer::logger::include_header_into_logfile("End: Copying installation set $installsetpath"); return $newpath; } ######################################################## # Renaming an existing Windows installation set. ######################################################## sub rename_install_set { my ( $installsetpath ) = @_; my $infoline = ""; my $dirname = $installsetpath; installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$dirname); my $path = $installsetpath; installer::pathanalyzer::get_path_from_fullqualifiedname(\$path); $path =~ s/\Q$installer::globals::separator\E\s*$//; if ( $dirname =~ /\./ ) { $dirname =~ s/\./_inprogress./; } else { $dirname = $dirname . "_inprogress"; } my $newpath = $path . $installer::globals::separator . $dirname; my $removepath = $newpath; $removepath =~ s/_inprogress/_witherror/; if ( -d $newpath ) { installer::systemactions::remove_complete_directory($newpath, 1); } if ( -d $removepath ) { installer::systemactions::remove_complete_directory($removepath, 1); } $installsetpath = installer::systemactions::rename_directory($installsetpath, $newpath); return $newpath; } ######################################################### # Checking the local system # Checking existence of needed files in include path ######################################################### sub check_system_path { # The following files have to be found in the environment variable PATH # Only, if \"-sign\" is used. # Windows : "msicert.exe", "diff.exe", "msidb.exe", "signtool.exe" my @needed_files_in_path = ("msicert.exe", "msidb.exe", "signtool.exe", "diff.exe"); if ( $installer::globals::internal_cabinet_signing ) { push(@needed_files_in_path, "cabarc.exe"); push(@needed_files_in_path, "makecab.exe"); } my $onefile; my $error = 0; my $pathvariable = $ENV{'PATH'}; my $local_pathseparator = $installer::globals::pathseparator; if( $^O =~ /cygwin/i ) { # When using cygwin's perl the PATH variable is POSIX style and ... $pathvariable = qx{cygpath -mp "$pathvariable"} ; # has to be converted to DOS style for further use. $local_pathseparator = ';'; } my $patharrayref = installer::converter::convert_stringlist_into_array(\$pathvariable, $local_pathseparator); $installer::globals::patharray = $patharrayref; foreach my $onefile ( @needed_files_in_path ) { $installer::logger::Info->printf("...... searching %s ...\n", $onefile); my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath_classic(\$onefile, $patharrayref , 0); if ( $$fileref eq "" ) { $error = 1; installer::logger::print_error( "$onefile not found\n" ); } else { $installer::logger::Info->printf("\tFound: %s\n", $$fileref); } } $installer::globals::signfiles_checked = 1; if ( $error ) { installer::exiter::exit_program("ERROR: Could not find all needed files in path!", "check_system_path"); } } ###################################################### # Making systemcall ###################################################### sub make_systemcall { my ($systemcall, $displaysystemcall) = @_; $installer::logger::Info->printf("... %s ...\n", $displaysystemcall); my $success = 1; my $returnvalue = system($systemcall); my $infoline = "Systemcall: $displaysystemcall\n"; $installer::logger::Lang->print($infoline); if ($returnvalue) { $infoline = "ERROR: Could not execute \"$displaysystemcall\"!\n"; $installer::logger::Lang->print($infoline); $success = 0; } else { $infoline = "Success: Executed \"$displaysystemcall\" successfully!\n"; $installer::logger::Lang->print($infoline); } return $success; } ###################################################### # Making systemcall with warning ###################################################### sub make_systemcall_with_warning { my ($systemcall, $displaysystemcall) = @_; $installer::logger::Info->printf("... %s ...\n", $displaysystemcall); my $success = 1; my $returnvalue = system($systemcall); my $infoline = "Systemcall: $displaysystemcall\n"; $installer::logger::Lang->print($infoline); if ($returnvalue) { $infoline = "WARNING: Could not execute \"$displaysystemcall\"!\n"; $installer::logger::Lang->print($infoline); $success = 0; } else { $infoline = "Success: Executed \"$displaysystemcall\" successfully!\n"; $installer::logger::Lang->print($infoline); } return $success; } ###################################################### # Making systemcall with more return data ###################################################### sub execute_open_system_call { my ( $systemcall ) = @_; my @openoutput = (); my $success = 1; my $comspec = $ENV{COMSPEC}; $comspec = $comspec . " -c "; if( $^O =~ /cygwin/i ) { # $comspec =~ s/\\/\\\\/g; # $comspec = qx{cygpath -u "$comspec"}; # $comspec =~ s/\s*$//g; $comspec = ""; } my $localsystemcall = "$comspec $systemcall 2>&1 |"; open( OPN, "$localsystemcall") or warn "Can't execute $localsystemcall\n"; while () { push(@openoutput, $_); } close (OPN); my $returnvalue = $?; # $? contains the return value of the systemcall if ($returnvalue) { $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; $installer::logger::Lang->print($infoline); $success = 0; } else { $infoline = "Success: Executed \"$systemcall\" successfully!\n"; $installer::logger::Lang->print($infoline); } return ($success, \@openoutput); } ######################################################## # Reading first line of pw file. ######################################################## sub get_pw { my ( $file ) = @_; my $filecontent = installer::files::read_file($file); my $pw = ${$filecontent}[0]; $pw =~ s/^\s*//; $pw =~ s/\s*$//; return $pw; } ######################################################## # Counting the keys of a hash. ######################################################## sub get_hash_count { my ($hashref) = @_; my $counter = 0; foreach my $key ( keys %{$hashref} ) { $counter++; } return $counter; } ############################################################ # Collect all last files in a cabinet file. This is # necessary to control, if the cabinet file was damaged # by calling signtool.exe. ############################################################ sub analyze_file_file { my ($filecontent) = @_; my %filenamehash = (); for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { next; } if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) { my $name = $1; my $sequence = $8; $filenamehash{$sequence} = $name; } } return ( \%filenamehash ); } ############################################################ # Collect all DiskIds to the corresponding cabinet files. ############################################################ sub analyze_media_file { my ($filecontent) = @_; my %diskidhash = (); my %lastsequencehash = (); for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { next; } if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) { my $diskid = $1; my $lastsequence = $2; my $cabfile = $4; $diskidhash{$cabfile} = $diskid; $lastsequencehash{$cabfile} = $lastsequence; } } return ( \%diskidhash, \%lastsequencehash ); } ######################################################## # Collect all DiskIds from database table "Media". ######################################################## sub collect_diskid_from_media_table { my ($msidatabase, $languagestring) = @_; # creating working directory my $workdir = installer::systemactions::create_directories("media", \$languagestring); installer::windows::admin::extract_tables_from_pcpfile($msidatabase, $workdir, "Media File"); # Reading tables my $filename = $workdir . $installer::globals::separator . "Media.idt"; if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find required file: $filename !", "collect_diskid_from_media_table"); } my $filecontent = installer::files::read_file($filename); my ( $diskidhash, $lastsequencehash ) = analyze_media_file($filecontent); $filename = $workdir . $installer::globals::separator . "File.idt"; if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find required file: $filename !", "collect_diskid_from_media_table"); } $filecontent = installer::files::read_file($filename); my $filenamehash = analyze_file_file($filecontent); return ( $diskidhash, $filenamehash, $lastsequencehash ); } ######################################################## # Check, if this installation set contains # internal cabinet files included into the msi # database. ######################################################## sub check_for_internal_cabfiles { my ($cabfilehash) = @_; my $contains_internal_cabfiles = 0; my %allcabfileshash = (); foreach my $filename ( keys %{$cabfilehash} ) { if ( $filename =~ /^\s*\#/ ) # starting with a hash { $contains_internal_cabfiles = 1; # setting real filename without hash as key and name with hash as value my $realfilename = $filename; $realfilename =~ s/^\s*\#//; $allcabfileshash{$realfilename} = $filename; } } return ( $contains_internal_cabfiles, \%allcabfileshash ); } ######################################################## # Collecting all files in an installation set. ######################################################## sub analyze_installset_content { my ( $installsetpath ) = @_; my @sourcefiles = (); my $pathstring = ""; installer::systemactions::read_complete_directory($installsetpath, $pathstring, \@sourcefiles); if ( ! ( $#sourcefiles > -1 )) { installer::exiter::exit_program("ERROR: No file in installation set. Path: $installsetpath !", "analyze_installset_content"); } my %allcabfileshash = (); my %allmsidatabaseshash = (); my %allfileshash = (); my $contains_external_cabfiles = 0; my $msidatabase = ""; my $contains_msidatabase = 0; for ( my $j = 0; $j <= $#sourcefiles; $j++ ) { if ( $sourcefiles[$j] =~ /\.cab\s*$/ ) { $allcabfileshash{$sourcefiles[$j]} = 1; } else { if ( $sourcefiles[$j] =~ /\.txt\s*$/ ) { next; } if ( $sourcefiles[$j] =~ /\.html\s*$/ ) { next; } if ( $sourcefiles[$j] =~ /\.ini\s*$/ ) { next; } if ( $sourcefiles[$j] =~ /\.bmp\s*$/ ) { next; } if ( $sourcefiles[$j] =~ /\.msi\s*$/ ) { if ( $msidatabase eq "" ) { $msidatabase = $sourcefiles[$j]; } else { installer::exiter::exit_program("ERROR: There is more than one msi database in installation set. Path: $installsetpath !", "analyze_installset_content"); } } $allfileshash{$sourcefiles[$j]} = 1; } } # Is there at least one cab file in the installation set? my $cabcounter = get_hash_count(\%allcabfileshash); if ( $cabcounter > 0 ) { $contains_external_cabfiles = 1; } # How about a cab file without a msi database? 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"); } if ( $msidatabase ne "" ) { $contains_msidatabase = 1; } return (\%allcabfileshash, \%allfileshash, $msidatabase, $contains_external_cabfiles, $contains_msidatabase, \@sourcefiles); } ######################################################## # Adding content of external cabinet files into the # msi database ######################################################## sub msicert_database { my ($msidatabase, $allcabfiles, $cabfilehash, $internalcabfile) = @_; my $fullsuccess = 1; foreach my $cabfile ( keys %{$allcabfiles} ) { my $origfilesize = -s $cabfile; my $mediacabfilename = $cabfile; if ( $internalcabfile ) { $mediacabfilename = "\#" . $mediacabfilename; } if ( ! exists($cabfilehash->{$mediacabfilename}) ) { installer::exiter::exit_program("ERROR: Could not determine DiskId from media table for cabinet file \"$cabfile\" !", "msicert_database"); } my $diskid = $cabfilehash->{$mediacabfilename}; my $systemcall = "msicert.exe -d $msidatabase -m $diskid -c $cabfile -h"; $success = make_systemcall($systemcall, $systemcall); if ( ! $success ) { $fullsuccess = 0; } # size of cabinet file must not change my $finalfilesize = -s $cabfile; if ( $origfilesize != $finalfilesize ) { installer::exiter::exit_program("ERROR: msicert.exe changed size of cabinet file !", "msicert_database"); } } return $fullsuccess; } ######################################################## # Checking if cabinet file was broken by signtool. ######################################################## sub cabinet_cosistency_check { my ( $onefile, $followmeinfohash, $filenamehash, $lastsequencehash, $temppath ) = @_; my $infoline = "Making consistency check of $onefile\n"; $installer::logger::Lang->print($infoline); my $expandfile = "expand.exe"; # Has to be in the path if ( $^O =~ /cygwin/i ) { $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe); chomp $expandfile; } if ( $filenamehash == 0 ) { $infoline = "Warning: Stopping consistency check: Important hash of filenames is empty!\n"; $installer::logger::Lang->print($infoline); } elsif ( $lastsequencehash == 0 ) { $infoline = "Warning: Stopping consistency check; Important hash of last sequences is empty!\n"; $installer::logger::Lang->print($infoline); } else # both hashes are available { # $onefile contains only the name of the cabinet file without path my $sequence = $lastsequencehash->{$onefile}; my $lastfile = $filenamehash->{$sequence}; $infoline = "Check of $onefile: Sequence: $sequence is file: $lastfile\n"; $installer::logger::Lang->print($infoline); # Therefore the file $lastfile need to be binary compared. # It has to be expanded from the cabinet file # of the original installation set and from the # newly signed cabinet file. # How about cabinet files extracted from msi database? my $finalinstalldir = $followmeinfohash->{'finalinstalldir'}; $finalinstalldir =~ s/\\\s*$//; $finalinstalldir =~ s/\/\s*$//; my $sourcecabfile = $finalinstalldir . $installer::globals::separator . $onefile; my $currentpath = cwd(); my $destcabfile = $currentpath . $installer::globals::separator . $onefile; # my $destcabfile = $onefile; if ( $^O =~ /cygwin/i ) { chomp( $destcabfile = qx{cygpath -w "$destcabfile"} ); $destcabfile =~ s/\\/\//g; } if ( ! -f $sourcecabfile ) { $infoline = "WARNING: Check of cab file cannot happen, because source cabinet file was not found: $sourcecabfile\n"; $installer::logger::Lang->print($infoline); } elsif ( ! -f $destcabfile ) { $infoline = "WARNING: Check of cab file cannot happen, because destination cabinet file was not found: $sourcecabfile\n"; $installer::logger::Lang->print($infoline); } else # everything is okay for the check { my $diffpath = get_diff_path($temppath); my $origdiffpath = $diffpath . $installer::globals::separator . "orig"; my $newdiffpath = $diffpath . $installer::globals::separator . "new"; if ( ! -d $origdiffpath ) { mkdir($origdiffpath); } if ( ! -d $newdiffpath ) { mkdir($newdiffpath); } my $systemcall = "$expandfile $sourcecabfile $origdiffpath -f:$lastfile "; $infoline = $systemcall . "\n"; $installer::logger::Lang->print($infoline); my $success = make_systemcall($systemcall, $systemcall); if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not successfully execute: $systemcall !", "cabinet_cosistency_check"); } $systemcall = "$expandfile $destcabfile $newdiffpath -f:$lastfile "; $infoline = $systemcall . "\n"; $installer::logger::Lang->print($infoline); $success = make_systemcall($systemcall, $systemcall); if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not successfully execute: $systemcall !", "cabinet_cosistency_check"); } # and finally the two files can be diffed. my $origfile = $origdiffpath . $installer::globals::separator . $lastfile; my $newfile = $newdiffpath . $installer::globals::separator . $lastfile; if ( ! -f $origfile ) { installer::exiter::exit_program("ERROR: Unpacked original file not found: $origfile !", "cabinet_cosistency_check"); } if ( ! -f $newfile ) { installer::exiter::exit_program("ERROR: Unpacked new file not found: $newfile !", "cabinet_cosistency_check"); } my $origsize = -s $origfile; my $newsize = -s $newfile; if ( $origsize != $newsize ) # This shows an error! { $infoline = "ERROR: Different filesize after signtool.exe was used. Original: $origsize Bytes, new: $newsize. File: $lastfile\n"; $installer::logger::Lang->print($infoline); installer::exiter::exit_program("ERROR: The cabinet file $destcabfile is broken after signtool.exe signed this file !", "cabinet_cosistency_check"); } else { $infoline = "Same size of last file in cabinet file after usage of signtool.exe: $newsize (File: $lastfile)\n"; $installer::logger::Lang->print($infoline); # Also making a binary diff? my $difffile = "diff.exe"; # has to be in the path # $systemcall = "$difffile $sourcecabfile $destcabfile"; # Test for differences $systemcall = "$difffile $origfile $newfile"; $infoline = $systemcall . "\n"; $returnvalue = make_systemcall($systemcall, $systemcall); my $success = $?; if ( $success == 0 ) { $infoline = "Last files are identical after signing cabinet file (File: $lastfile)\n"; $installer::logger::Lang->print($infoline); } elsif ( $success == 1 ) { $infoline = "ERROR: Last files are different after signing cabinet file (File: $lastfile)\n"; $installer::logger::Lang->print($infoline); installer::exiter::exit_program("ERROR: Last files are different after signing cabinet file (File: $lastfile)!", "cabinet_cosistency_check"); } else { $infoline = "ERROR: Problem occured calling diff.exe (File: $lastfile)\n"; $installer::logger::Lang->print($infoline); installer::exiter::exit_program("ERROR: Problem occured calling diff.exe (File: $lastfile) !", "cabinet_cosistency_check"); } } } } } ######################################################## # Signing a list of files ######################################################## sub sign_files { my ( $followmeinfohash, $allfiles, $pw, $cabinternal, $filenamehash, $lastsequencehash, $temppath ) = @_; my $infoline = ""; my $fullsuccess = 1; my $maxcounter = 3; my $productname = ""; if ( $followmeinfohash->{'allvariableshash'}->{'PRODUCTNAME'} ) { $productname = "/d " . "\"$followmeinfohash->{'allvariableshash'}->{'PRODUCTNAME'}\""; } my $url = "/du " . "\"http://www.openoffice.org\""; my $timestampurl = "http://timestamp.verisign.com/scripts/timestamp.dll"; my $pfxfilepath = $installer::globals::pfxfile; if( $^O =~ /cygwin/i ) { $pfxfilepath = qx{cygpath -w "$pfxfilepath"}; $pfxfilepath =~ s/\\/\\\\/g; $pfxfilepath =~ s/\s*$//g; } foreach my $onefile ( reverse sort keys %{$allfiles} ) { if ( already_certified($onefile) ) { $infoline = "Already certified: Skipping file $onefile\n"; $installer::logger::Lang->print($infoline); next; } my $counter = 1; my $success = 0; while (( $counter <= $maxcounter ) && ( ! $success )) { if ( $counter > 1 ) { $installer::logger::Info->printf("\n"); $installer::logger::Info->printf("\n"); $installer::logger::Info->printf("... repeating file %s ...\n", $onefile); } if ( $cabinternal ) { $installer::logger::Info->printf(" Signing: %s\n", $onefile); } my $systemcall = "signtool.exe sign /f \"$pfxfilepath\" /p $pw $productname $url /t \"$timestampurl\" \"$onefile\""; my $displaysystemcall = "signtool.exe sign /f \"$pfxfilepath\" /p ***** $productname $url /t \"$timestampurl\" \"$onefile\""; $success = make_systemcall_with_warning($systemcall, $displaysystemcall); $counter++; } # Special check for cabinet files, that sometimes get damaged by signtool.exe if (( $success ) && ( $onefile =~ /\.cab\s*$/ ) && ( ! $cabinternal )) { cabinet_cosistency_check($onefile, $followmeinfohash, $filenamehash, $lastsequencehash, $temppath); } if ( ! $success ) { $fullsuccess = 0; installer::exiter::exit_program("ERROR: Could not sign file: $onefile!", "sign_files"); } } return $fullsuccess; } ########################################################################## # Lines in ddf files must not contain more than 256 characters ########################################################################## sub check_ddf_file { my ( $ddffile, $ddffilename ) = @_; my $maxlength = 0; my $maxline = 0; my $linelength = 0; my $linenumber = 0; for ( my $i = 0; $i <= $#{$ddffile}; $i++ ) { my $oneline = ${$ddffile}[$i]; $linelength = length($oneline); $linenumber = $i + 1; if ( $linelength > 256 ) { installer::exiter::exit_program("ERROR \"$ddffilename\" line $linenumber: Lines in ddf files must not contain more than 256 characters!", "check_ddf_file"); } if ( $linelength > $maxlength ) { $maxlength = $linelength; $maxline = $linenumber; } } my $infoline = "Check of ddf file \"$ddffilename\": Maximum length \"$maxlength\" in line \"$maxline\" (allowed line length: 256 characters)\n"; $installer::logger::Lang->print($infoline); } ################################################################# # Setting the path, where the cab files are unpacked. ################################################################# sub get_cab_path { my ($temppath) = @_; my $cabpath = "cabs_" . $$; $cabpath = $temppath . $installer::globals::separator . $cabpath; if ( ! -d $cabpath ) { installer::systemactions::create_directory($cabpath); } return $cabpath; } ################################################################# # Setting the path, where the diff can happen. ################################################################# sub get_diff_path { my ($temppath) = @_; my $diffpath = "diff_" . $$; $diffpath = $temppath . $installer::globals::separator . $diffpath; if ( ! -d $diffpath ) { installer::systemactions::create_directory($diffpath); } return $diffpath; } ################################################################# # Exclude all cab files from the msi database. ################################################################# sub extract_cabs_from_database { my ($msidatabase, $allcabfiles) = @_; installer::logger::include_header_into_logfile("Extracting cabs from msi database"); my $infoline = ""; my $fullsuccess = 1; my $msidb = "msidb.exe"; # Has to be in the path # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) $msidatabase =~ s/\//\\\\/g; foreach my $onefile ( keys %{$allcabfiles} ) { my $systemcall = $msidb . " -d " . $msidatabase . " -x " . $onefile; my $success = make_systemcall($systemcall, $systemcall); if ( ! $success ) { $fullsuccess = 0; } # and removing the stream from the database $systemcall = $msidb . " -d " . $msidatabase . " -k " . $onefile; $success = make_systemcall($systemcall, $systemcall); if ( ! $success ) { $fullsuccess = 0; } } return $fullsuccess; } ################################################################# # Include cab files into the msi database. ################################################################# sub include_cabs_into_database { my ($msidatabase, $allcabfiles) = @_; installer::logger::include_header_into_logfile("Including cabs into msi database"); my $infoline = ""; my $fullsuccess = 1; my $msidb = "msidb.exe"; # Has to be in the path # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) $msidatabase =~ s/\//\\\\/g; foreach my $onefile ( keys %{$allcabfiles} ) { my $systemcall = $msidb . " -d " . $msidatabase . " -a " . $onefile; my $success = make_systemcall($systemcall, $systemcall); if ( ! $success ) { $fullsuccess = 0; } } return $fullsuccess; } ######################################################## # Reading the order of the files inside the # cabinet files. ######################################################## sub read_cab_file { my ($cabfilename) = @_; $installer::logger::Info->printf("\n"); $installer::logger::Info->printf("... reading cabinet file %s ...\n", $cabfilename); my $infoline = "Reading cabinet file $cabfilename\n"; $installer::logger::Lang->print($infoline); my $systemcall = "cabarc.exe" . " L " . $cabfilename; push(@logfile, "$systemcall\n"); my ($success, $fileorder) = execute_open_system_call($systemcall); my @allfiles = (); for ( my $i = 0; $i <= $#{$fileorder}; $i++ ) { my $line = ${$fileorder}[$i]; if ( $line =~ /^\s*(.*?)\s+\d+\s+\d+\/\d+\/\d+\s+\d+\:\d+\:\d+\s+[\w-]+\s*$/ ) { my $filename = $1; push(@allfiles, $filename); } } return \@allfiles; } ######################################################## # Unpacking a cabinet file. ######################################################## sub unpack_cab_file { my ($cabfilename, $temppath) = @_; $installer::logger::Info->printf("\n"); $installer::logger::Info->printf("... unpacking cabinet file %s ...\n", $cabfilename); my $infoline = "Unpacking cabinet file $cabfilename\n"; $installer::logger::Lang->print($infoline); my $dirname = $cabfilename; $dirname =~ s/\.cab\s*$//; my $workingpath = $temppath . $installer::globals::separator . "unpack_". $dirname . "_" . $$; if ( ! -d $workingpath ) { installer::systemactions::create_directory($workingpath); } # changing into unpack directory my $from = cwd(); chdir($workingpath); my $fullcabfilename = $from . $installer::globals::separator . $cabfilename; if( $^O =~ /cygwin/i ) { $fullcabfilename = qx{cygpath -w "$fullcabfilename"}; $fullcabfilename =~ s/\\/\\\\/g; $fullcabfilename =~ s/\s*$//g; } my $systemcall = "cabarc.exe" . " -p X " . $fullcabfilename; $success = make_systemcall($systemcall, $systemcall); if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not unpack cabinet file: $fullcabfilename!", "unpack_cab_file"); } # returning to directory chdir($from); return $workingpath; } ######################################################## # Returning the header of a ddf file. ######################################################## sub get_ddf_file_header { my ($ddffileref, $cabinetfile, $installdir) = @_; my $oneline; my $compressionlevel = 2; if( $^O =~ /cygwin/i ) { $installdir = qx{cygpath -w "$installdir"}; $installdir =~ s/\s*$//g; } $oneline = ".Set CabinetName1=" . $cabinetfile . "\n"; push(@{$ddffileref} ,$oneline); $oneline = ".Set ReservePerCabinetSize=128\n"; # This reserves space for a digital signature. push(@{$ddffileref} ,$oneline); $oneline = ".Set MaxDiskSize=2147483648\n"; # This allows the .cab file to get a size of 2 GB. push(@{$ddffileref} ,$oneline); $oneline = ".Set CompressionType=LZX\n"; push(@{$ddffileref} ,$oneline); $oneline = ".Set Compress=ON\n"; push(@{$ddffileref} ,$oneline); $oneline = ".Set CompressionLevel=$compressionlevel\n"; push(@{$ddffileref} ,$oneline); $oneline = ".Set Cabinet=ON\n"; push(@{$ddffileref} ,$oneline); $oneline = ".Set DiskDirectoryTemplate=" . $installdir . "\n"; push(@{$ddffileref} ,$oneline); } ######################################################## # Writing content into ddf file. ######################################################## sub put_all_files_into_ddffile { my ($ddffile, $allfiles, $workingpath) = @_; $workingpath =~ s/\//\\/g; for ( my $i = 0; $i <= $#{$allfiles}; $i++ ) { my $filename = ${$allfiles}[$i]; if( $^O =~ /cygwin/i ) { $filename =~ s/\//\\/g; } # Backslash for Cygwin! if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file: $filename!", "put_all_files_into_ddffile"); } my $infoline = "\"" . $filename . "\"" . " " . ${$allfiles}[$i] . "\n"; push( @{$ddffile}, $infoline); } } ######################################################## # Packing a cabinet file. ######################################################## sub do_pack_cab_file { my ($cabfilename, $allfiles, $workingpath, $temppath) = @_; $installer::logger::Info->print("\n"); $installer::logger::Info->printf("... packing cabinet file %s ...\n", $cabfilename); my $infoline = "Packing cabinet file $cabfilename\n"; $installer::logger::Lang->print($infoline); if ( -f $cabfilename ) { unlink($cabfilename); } # removing cab file if ( -f $cabfilename ) { installer::exiter::exit_program("ERROR: Failed to remove file: $cabfilename!", "do_pack_cab_file"); } # generate ddf file for makecab.exe my @ddffile = (); my $dirname = $cabfilename; $dirname =~ s/\.cab\s*$//; my $ddfpath = $temppath . $installer::globals::separator . "ddf_". $dirname . "_" . $$; my $ddffilename = $cabfilename; $ddffilename =~ s/.cab/.ddf/; $ddffilename = $ddfpath . $installer::globals::separator . $ddffilename; if ( ! -d $ddfpath ) { installer::systemactions::create_directory($ddfpath); } my $from = cwd(); chdir($workingpath); # changing into the directory with the unpacked files get_ddf_file_header(\@ddffile, $cabfilename, $from); put_all_files_into_ddffile(\@ddffile, $allfiles, $workingpath); # lines in ddf files must not be longer than 256 characters check_ddf_file(\@ddffile, $ddffilename); installer::files::save_file($ddffilename, \@ddffile); if( $^O =~ /cygwin/i ) { $ddffilename = qx{cygpath -w "$ddffilename"}; $ddffilename =~ s/\\/\\\\/g; $ddffilename =~ s/\s*$//g; } my $systemcall = "makecab.exe /V1 /F " . $ddffilename; my $success = make_systemcall($systemcall, $systemcall); if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not pack cabinet file!", "do_pack_cab_file"); } chdir($from); return ($success); } ######################################################## # Extraction the file extension from a file ######################################################## sub get_extension { my ( $file ) = @_; my $extension = ""; if ( $file =~ /^\s*(.*)\.(\w+?)\s*$/ ) { $extension = $2; } return $extension; } ######################################################## # Checking, if a file already contains a certificate. # This must not be overwritten. ######################################################## sub already_certified { my ( $filename ) = @_; my $success = 1; my $is_certified = 0; my $systemcall = "signtool.exe verify /q /pa \"$filename\""; my $returnvalue = system($systemcall); if ( $returnvalue ) { $success = 0; } # my $success = make_systemcall($systemcall, $systemcall); if ( $success ) { $is_certified = 1; $installer::logger::Info->printf("... already certified -> skipping %s ...\n", $filename); } return $is_certified; } ######################################################## # Signing the files, that are included into # cabinet files. ######################################################## sub sign_files_in_cabinet_files { my ( $followmeinfohash, $allcabfiles, $pw, $temppath ) = @_; my $complete_success = 1; my $from = cwd(); foreach my $cabfilename ( keys %{$allcabfiles} ) { my $success = 1; # saving order of files in cab file my $fileorder = read_cab_file($cabfilename); # unpack into $working path my $workingpath = unpack_cab_file($cabfilename, $temppath); chdir($workingpath); # sign files my %allfileshash = (); foreach my $onefile ( @{$fileorder} ) { my $extension = get_extension($onefile); if ( exists( $installer::globals::sign_extensions{$extension} ) ) { $allfileshash{$onefile} = 1; } } $success = sign_files($followmeinfohash, \%allfileshash, $pw, 1, 0, 0, $temppath); if ( ! $success ) { $complete_success = 0; } chdir($from); # pack into new directory do_pack_cab_file($cabfilename, $fileorder, $workingpath, $temppath); } return $complete_success; } ######################################################## # Comparing the content of two directories. # Only filesize is compared. ######################################################## sub compare_directories { my ( $dir1, $dir2, $files ) = @_; $dir1 =~ s/\\\s*//; $dir2 =~ s/\\\s*//; $dir1 =~ s/\/\s*//; $dir2 =~ s/\/\s*//; my $infoline = "Comparing directories: $dir1 and $dir2\n"; $installer::logger::Lang->print($infoline); foreach my $onefile ( @{$files} ) { my $file1 = $dir1 . $installer::globals::separator . $onefile; my $file2 = $dir2 . $installer::globals::separator . $onefile; if ( ! -f $file1 ) { installer::exiter::exit_program("ERROR: Missing file : $file1!", "compare_directories"); } if ( ! -f $file2 ) { installer::exiter::exit_program("ERROR: Missing file : $file2!", "compare_directories"); } my $size1 = -s $file1; my $size2 = -s $file2; $infoline = "Comparing files: $file1 ($size1) and $file2 ($size2)\n"; $installer::logger::Lang->print($infoline); if ( $size1 != $size2 ) { installer::exiter::exit_program("ERROR: File defect after copy (different size) $file1 ($size1 bytes) and $file2 ($size2 bytes)!", "compare_directories"); } } } ######################################################## # Signing an existing Windows installation set. ######################################################## sub sign_install_set { my ($followmeinfohash, $make_copy, $temppath) = @_; my $installsetpath = $followmeinfohash->{'finalinstalldir'}; installer::logger::include_header_into_logfile("Start: Signing installation set $installsetpath"); my $complete_success = 1; my $success = 1; my $infoline = "Signing installation set in $installsetpath\n"; $installer::logger::Lang->print($infoline); # check required files. if ( ! $installer::globals::signfiles_checked ) { check_system_path(); } # get cerficate information my $pw = get_pw($installer::globals::pwfile); # making a copy of the installation set, if required if ( $make_copy ) { $installsetpath = copy_install_set($installsetpath); } else { $installsetpath = rename_install_set($installsetpath); } # collecting all files in the installation set my ($allcabfiles, $allfiles, $msidatabase, $contains_external_cabfiles, $contains_msidatabase, $sourcefiles) = analyze_installset_content($installsetpath); if ( $make_copy ) { compare_directories($installsetpath, $followmeinfohash->{'finalinstalldir'}, $sourcefiles); } # changing into installation set my $from = cwd(); my $fullmsidatabase = $installsetpath . $installer::globals::separator . $msidatabase; if( $^O =~ /cygwin/i ) { $fullmsidatabase = qx{cygpath -w "$fullmsidatabase"}; $fullmsidatabase =~ s/\\/\\\\/g; $fullmsidatabase =~ s/\s*$//g; } chdir($installsetpath); if ( $contains_msidatabase ) { # exclude media table from msi database and get all diskids. my ( $cabfilehash, $filenamehash, $lastsequencehash ) = collect_diskid_from_media_table($msidatabase, $followmeinfohash->{'languagestring'}); # Check, if there are internal cab files my ( $contains_internal_cabfiles, $all_internal_cab_files) = check_for_internal_cabfiles($cabfilehash); if ( $contains_internal_cabfiles ) { my $cabpath = get_cab_path($temppath); chdir($cabpath); # Exclude all cabinet files from database $success = extract_cabs_from_database($fullmsidatabase, $all_internal_cab_files); if ( ! $success ) { $complete_success = 0; } if ( $installer::globals::internal_cabinet_signing ) { sign_files_in_cabinet_files($followmeinfohash, $all_internal_cab_files, $pw, $temppath); } $success = sign_files($followmeinfohash, $all_internal_cab_files, $pw, 0, $filenamehash, $lastsequencehash, $temppath); if ( ! $success ) { $complete_success = 0; } $success = msicert_database($fullmsidatabase, $all_internal_cab_files, $cabfilehash, 1); if ( ! $success ) { $complete_success = 0; } # Include all cabinet files into database $success = include_cabs_into_database($fullmsidatabase, $all_internal_cab_files); if ( ! $success ) { $complete_success = 0; } chdir($installsetpath); } # Warning: There might be a problem with very big cabinet files # signing all external cab files first if ( $contains_external_cabfiles ) { if ( $installer::globals::internal_cabinet_signing ) { sign_files_in_cabinet_files($followmeinfohash, $allcabfiles, $pw, $temppath); } $success = sign_files($followmeinfohash, $allcabfiles, $pw, 0, $filenamehash, $lastsequencehash, $temppath); if ( ! $success ) { $complete_success = 0; } $success = msicert_database($msidatabase, $allcabfiles, $cabfilehash, 0); if ( ! $success ) { $complete_success = 0; } } } # finally all other files can be signed $success = sign_files($followmeinfohash, $allfiles, $pw, 0, 0, 0, $temppath); if ( ! $success ) { $complete_success = 0; } # and changing back chdir($from); installer::logger::include_header_into_logfile("End: Signing installation set $installsetpath"); return ($installsetpath); } 1;