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