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 = "/du " . "\"http://www.openoffice.org\"";
658	my $timestampurl = "http://timestamp.verisign.com/scripts/timestamp.dll";
659
660	my $pfxfilepath = $installer::globals::pfxfile;
661
662	if( $^O =~ /cygwin/i )
663	{
664		$pfxfilepath = qx{cygpath -w "$pfxfilepath"};
665		$pfxfilepath =~ s/\\/\\\\/g;
666		$pfxfilepath =~ s/\s*$//g;
667	}
668
669	foreach my $onefile ( reverse sort keys %{$allfiles} )
670	{
671		if ( already_certified($onefile) )
672		{
673			$infoline = "Already certified: Skipping file $onefile\n";
674			push( @installer::globals::logfileinfo, $infoline);
675			next;
676		}
677
678		my $counter = 1;
679		my $success = 0;
680
681		while (( $counter <= $maxcounter ) && ( ! $success ))
682		{
683			if ( $counter > 1 ) { installer::logger::print_message( "\n\n... repeating file $onefile ...\n" ); }
684			if ( $cabinternal ) { installer::logger::print_message("    Signing: $onefile\n"); }
685			my $systemcall = "signtool.exe sign /f \"$pfxfilepath\" /p $pw $productname $url /t \"$timestampurl\" \"$onefile\"";
686			my $displaysystemcall = "signtool.exe sign /f \"$pfxfilepath\" /p ***** $productname $url /t \"$timestampurl\" \"$onefile\"";
687	 		$success = make_systemcall_with_warning($systemcall, $displaysystemcall);
688	 		$counter++;
689	 	}
690
691	 	# Special check for cabinet files, that sometimes get damaged by signtool.exe
692	 	if (( $success ) && ( $onefile =~ /\.cab\s*$/ ) && ( ! $cabinternal ))
693	 	{
694	 		cabinet_cosistency_check($onefile, $followmeinfohash, $filenamehash, $lastsequencehash, $temppath);
695		}
696
697 		if ( ! $success )
698 		{
699 			$fullsuccess = 0;
700			installer::exiter::exit_program("ERROR: Could not sign file: $onefile!", "sign_files");
701		}
702	}
703
704	return $fullsuccess;
705}
706
707##########################################################################
708# Lines in ddf files must not contain more than 256 characters
709##########################################################################
710
711sub check_ddf_file
712{
713	my ( $ddffile, $ddffilename ) = @_;
714
715	my $maxlength = 0;
716	my $maxline = 0;
717	my $linelength = 0;
718	my $linenumber = 0;
719
720	for ( my $i = 0; $i <= $#{$ddffile}; $i++ )
721	{
722		my $oneline = ${$ddffile}[$i];
723
724		$linelength = length($oneline);
725		$linenumber = $i + 1;
726
727		if ( $linelength > 256 )
728		{
729			installer::exiter::exit_program("ERROR \"$ddffilename\" line $linenumber: Lines in ddf files must not contain more than 256 characters!", "check_ddf_file");
730		}
731
732		if ( $linelength > $maxlength )
733		{
734			$maxlength = $linelength;
735			$maxline = $linenumber;
736		}
737	}
738
739	my $infoline = "Check of ddf file \"$ddffilename\": Maximum length \"$maxlength\" in line \"$maxline\" (allowed line length: 256 characters)\n";
740	push( @installer::globals::logfileinfo, $infoline);
741}
742
743#################################################################
744# Setting the path, where the cab files are unpacked.
745#################################################################
746
747sub get_cab_path
748{
749	my ($temppath) = @_;
750
751	my $cabpath = "cabs_" . $$;
752	$cabpath = $temppath . $installer::globals::separator . $cabpath;
753	if ( ! -d $cabpath ) { installer::systemactions::create_directory($cabpath); }
754
755	return $cabpath;
756}
757
758#################################################################
759# Setting the path, where the diff can happen.
760#################################################################
761
762sub get_diff_path
763{
764	my ($temppath) = @_;
765
766	my $diffpath = "diff_" . $$;
767	$diffpath = $temppath . $installer::globals::separator . $diffpath;
768	if ( ! -d $diffpath ) { installer::systemactions::create_directory($diffpath); }
769
770	return $diffpath;
771}
772
773#################################################################
774# Exclude all cab files from the msi database.
775#################################################################
776
777sub extract_cabs_from_database
778{
779	my ($msidatabase, $allcabfiles) = @_;
780
781	installer::logger::include_header_into_logfile("Extracting cabs from msi database");
782
783	my $infoline = "";
784	my $fullsuccess = 1;
785	my $msidb = "msidb.exe";	# Has to be in the path
786
787	# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
788	$msidatabase =~ s/\//\\\\/g;
789
790	foreach my $onefile ( keys %{$allcabfiles} )
791	{
792		my $systemcall = $msidb . " -d " . $msidatabase . " -x " . $onefile;
793 		my $success = make_systemcall($systemcall, $systemcall);
794		if ( ! $success ) { $fullsuccess = 0; }
795
796		# and removing the stream from the database
797		$systemcall = $msidb . " -d " . $msidatabase . " -k " . $onefile;
798 		$success = make_systemcall($systemcall, $systemcall);
799		if ( ! $success ) { $fullsuccess = 0; }
800	}
801
802	return $fullsuccess;
803}
804
805#################################################################
806# Include cab files into the msi database.
807#################################################################
808
809sub include_cabs_into_database
810{
811	my ($msidatabase, $allcabfiles) = @_;
812
813	installer::logger::include_header_into_logfile("Including cabs into msi database");
814
815	my $infoline = "";
816	my $fullsuccess = 1;
817	my $msidb = "msidb.exe";	# Has to be in the path
818
819	# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
820	$msidatabase =~ s/\//\\\\/g;
821
822	foreach my $onefile ( keys %{$allcabfiles} )
823	{
824		my $systemcall = $msidb . " -d " . $msidatabase . " -a " . $onefile;
825 		my $success = make_systemcall($systemcall, $systemcall);
826		if ( ! $success ) { $fullsuccess = 0; }
827	}
828
829	return $fullsuccess;
830}
831
832########################################################
833# Reading the order of the files inside the
834# cabinet files.
835########################################################
836
837sub read_cab_file
838{
839	my ($cabfilename) = @_;
840
841	installer::logger::print_message( "\n... reading cabinet file $cabfilename ...\n" );
842	my $infoline = "Reading cabinet file $cabfilename\n";
843	push( @installer::globals::logfileinfo, $infoline);
844
845	my $systemcall = "cabarc.exe" . " L " . $cabfilename;
846	push(@logfile, "$systemcall\n");
847
848	my ($success, $fileorder) = execute_open_system_call($systemcall);
849
850	my @allfiles = ();
851
852	for ( my $i = 0; $i <= $#{$fileorder}; $i++ )
853	{
854		my $line = ${$fileorder}[$i];
855		if ( $line =~ /^\s*(.*?)\s+\d+\s+\d+\/\d+\/\d+\s+\d+\:\d+\:\d+\s+[\w-]+\s*$/ )
856		{
857			my $filename = $1;
858			push(@allfiles, $filename);
859		}
860	}
861
862	return \@allfiles;
863}
864
865########################################################
866# Unpacking a cabinet file.
867########################################################
868
869sub unpack_cab_file
870{
871	my ($cabfilename, $temppath) = @_;
872
873	installer::logger::print_message( "\n... unpacking cabinet file $cabfilename ...\n" );
874	my $infoline = "Unpacking cabinet file $cabfilename\n";
875	push( @installer::globals::logfileinfo, $infoline);
876
877	my $dirname = $cabfilename;
878	$dirname =~ s/\.cab\s*$//;
879	my $workingpath = $temppath . $installer::globals::separator . "unpack_". $dirname . "_" . $$;
880	if ( ! -d $workingpath ) { installer::systemactions::create_directory($workingpath); }
881
882	# changing into unpack directory
883	my $from = cwd();
884	chdir($workingpath);
885
886	my $fullcabfilename = $from . $installer::globals::separator . $cabfilename;
887
888	if( $^O =~ /cygwin/i )
889	{
890		$fullcabfilename = qx{cygpath -w "$fullcabfilename"};
891		$fullcabfilename =~ s/\\/\\\\/g;
892		$fullcabfilename =~ s/\s*$//g;
893	}
894
895	my $systemcall = "cabarc.exe" . " -p X " . $fullcabfilename;
896	$success = make_systemcall($systemcall, $systemcall);
897	if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not unpack cabinet file: $fullcabfilename!", "unpack_cab_file"); }
898
899	# returning to directory
900	chdir($from);
901
902	return $workingpath;
903}
904
905########################################################
906# Returning the header of a ddf file.
907########################################################
908
909sub get_ddf_file_header
910{
911	my ($ddffileref, $cabinetfile, $installdir) = @_;
912
913	my $oneline;
914	my $compressionlevel = 2;
915
916	if( $^O =~ /cygwin/i )
917	{
918		$installdir = qx{cygpath -w "$installdir"};
919		$installdir =~ s/\s*$//g;
920	}
921
922	$oneline = ".Set CabinetName1=" . $cabinetfile . "\n";
923	push(@{$ddffileref} ,$oneline);
924	$oneline = ".Set ReservePerCabinetSize=128\n";	# This reserves space for a digital signature.
925	push(@{$ddffileref} ,$oneline);
926	$oneline = ".Set MaxDiskSize=2147483648\n";		# This allows the .cab file to get a size of 2 GB.
927	push(@{$ddffileref} ,$oneline);
928	$oneline = ".Set CompressionType=LZX\n";
929	push(@{$ddffileref} ,$oneline);
930	$oneline = ".Set Compress=ON\n";
931	push(@{$ddffileref} ,$oneline);
932	$oneline = ".Set CompressionLevel=$compressionlevel\n";
933	push(@{$ddffileref} ,$oneline);
934	$oneline = ".Set Cabinet=ON\n";
935	push(@{$ddffileref} ,$oneline);
936	$oneline = ".Set DiskDirectoryTemplate=" . $installdir . "\n";
937	push(@{$ddffileref} ,$oneline);
938}
939
940########################################################
941# Writing content into ddf file.
942########################################################
943
944sub put_all_files_into_ddffile
945{
946	my ($ddffile, $allfiles, $workingpath) = @_;
947
948	$workingpath =~ s/\//\\/g;
949
950	for ( my $i = 0; $i <= $#{$allfiles}; $i++ )
951	{
952		my $filename = ${$allfiles}[$i];
953		if( $^O =~ /cygwin/i ) { $filename =~ s/\//\\/g; } # Backslash for Cygwin!
954		if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file: $filename!", "put_all_files_into_ddffile"); }
955		my $infoline = "\"" . $filename . "\"" . " " . ${$allfiles}[$i] . "\n";
956		push( @{$ddffile}, $infoline);
957	}
958}
959
960########################################################
961# Packing a cabinet file.
962########################################################
963
964sub do_pack_cab_file
965{
966	my ($cabfilename, $allfiles, $workingpath, $temppath) = @_;
967
968	installer::logger::print_message( "\n... packing cabinet file $cabfilename ...\n" );
969	my $infoline = "Packing cabinet file $cabfilename\n";
970	push( @installer::globals::logfileinfo, $infoline);
971
972	if ( -f $cabfilename ) { unlink($cabfilename); } # removing cab file
973	if ( -f $cabfilename ) { installer::exiter::exit_program("ERROR: Failed to remove file: $cabfilename!", "do_pack_cab_file"); }
974
975	# generate ddf file for makecab.exe
976	my @ddffile = ();
977
978	my $dirname = $cabfilename;
979	$dirname =~ s/\.cab\s*$//;
980	my $ddfpath = $temppath . $installer::globals::separator . "ddf_". $dirname . "_" . $$;
981
982	my $ddffilename = $cabfilename;
983	$ddffilename =~ s/.cab/.ddf/;
984	$ddffilename = $ddfpath . $installer::globals::separator . $ddffilename;
985
986	if ( ! -d $ddfpath ) { installer::systemactions::create_directory($ddfpath); }
987
988	my $from = cwd();
989
990	chdir($workingpath); # changing into the directory with the unpacked files
991
992	get_ddf_file_header(\@ddffile, $cabfilename, $from);
993	put_all_files_into_ddffile(\@ddffile, $allfiles, $workingpath);
994	# lines in ddf files must not be longer than 256 characters
995	check_ddf_file(\@ddffile, $ddffilename);
996
997	installer::files::save_file($ddffilename, \@ddffile);
998
999	if( $^O =~ /cygwin/i )
1000	{
1001		$ddffilename = qx{cygpath -w "$ddffilename"};
1002		$ddffilename =~ s/\\/\\\\/g;
1003		$ddffilename =~ s/\s*$//g;
1004	}
1005
1006	my $systemcall = "makecab.exe /V1 /F " . $ddffilename;
1007	my $success = make_systemcall($systemcall, $systemcall);
1008	if ( ! $success ) { installer::exiter::exit_program("ERROR: Could not pack cabinet file!", "do_pack_cab_file"); }
1009
1010	chdir($from);
1011
1012	return ($success);
1013}
1014
1015########################################################
1016# Extraction the file extension from a file
1017########################################################
1018
1019sub get_extension
1020{
1021	my ( $file ) = @_;
1022
1023	my $extension = "";
1024
1025	if ( $file =~ /^\s*(.*)\.(\w+?)\s*$/ ) { $extension = $2; }
1026
1027	return $extension;
1028}
1029
1030########################################################
1031# Checking, if a file already contains a certificate.
1032# This must not be overwritten.
1033########################################################
1034
1035sub already_certified
1036{
1037	my ( $filename ) = @_;
1038
1039	my $success = 1;
1040	my $is_certified = 0;
1041
1042	my $systemcall = "signtool.exe verify /q /pa \"$filename\"";
1043	my $returnvalue = system($systemcall);
1044
1045	if ( $returnvalue ) { $success = 0; }
1046
1047	# my $success = make_systemcall($systemcall, $systemcall);
1048
1049 	if ( $success )
1050 	{
1051 		$is_certified = 1;
1052		installer::logger::print_message( "... already certified -> skipping $filename ...\n" );
1053	}
1054
1055	return $is_certified;
1056}
1057
1058########################################################
1059# Signing the files, that are included into
1060# cabinet files.
1061########################################################
1062
1063sub sign_files_in_cabinet_files
1064{
1065	my ( $followmeinfohash, $allcabfiles, $pw, $temppath ) = @_;
1066
1067	my $complete_success = 1;
1068	my $from = cwd();
1069
1070	foreach my $cabfilename ( keys %{$allcabfiles} )
1071	{
1072		my $success = 1;
1073
1074		# saving order of files in cab file
1075		my $fileorder = read_cab_file($cabfilename);
1076
1077		# unpack into $working path
1078		my $workingpath = unpack_cab_file($cabfilename, $temppath);
1079
1080		chdir($workingpath);
1081
1082		# sign files
1083		my %allfileshash = ();
1084		foreach my $onefile ( @{$fileorder} )
1085		{
1086			my $extension = get_extension($onefile);
1087			if ( exists( $installer::globals::sign_extensions{$extension} ) )
1088			{
1089				$allfileshash{$onefile} = 1;
1090			}
1091		}
1092 		$success = sign_files($followmeinfohash, \%allfileshash, $pw, 1, 0, 0, $temppath);
1093		if ( ! $success ) { $complete_success = 0; }
1094
1095		chdir($from);
1096
1097		# pack into new directory
1098		do_pack_cab_file($cabfilename, $fileorder, $workingpath, $temppath);
1099	}
1100
1101	return $complete_success;
1102}
1103
1104########################################################
1105# Comparing the content of two directories.
1106# Only filesize is compared.
1107########################################################
1108
1109sub compare_directories
1110{
1111	my ( $dir1, $dir2, $files ) = @_;
1112
1113	$dir1 =~ s/\\\s*//;
1114	$dir2 =~ s/\\\s*//;
1115	$dir1 =~ s/\/\s*//;
1116	$dir2 =~ s/\/\s*//;
1117
1118	my $infoline = "Comparing directories: $dir1 and $dir2\n";
1119	push( @installer::globals::logfileinfo, $infoline);
1120
1121	foreach my $onefile ( @{$files} )
1122	{
1123		my $file1 = $dir1 . $installer::globals::separator . $onefile;
1124		my $file2 = $dir2 . $installer::globals::separator . $onefile;
1125
1126		if ( ! -f $file1 ) { installer::exiter::exit_program("ERROR: Missing file : $file1!", "compare_directories"); }
1127		if ( ! -f $file2 ) { installer::exiter::exit_program("ERROR: Missing file : $file2!", "compare_directories"); }
1128
1129		my $size1 = -s $file1;
1130		my $size2 = -s $file2;
1131
1132		$infoline = "Comparing files: $file1 ($size1) and $file2 ($size2)\n";
1133		push( @installer::globals::logfileinfo, $infoline);
1134
1135		if ( $size1 != $size2 )
1136		{
1137			installer::exiter::exit_program("ERROR: File defect after copy (different size) $file1 ($size1 bytes) and $file2 ($size2 bytes)!", "compare_directories");
1138		}
1139	}
1140}
1141
1142########################################################
1143# Signing an existing Windows installation set.
1144########################################################
1145
1146sub sign_install_set
1147{
1148	my ($followmeinfohash, $make_copy, $temppath) = @_;
1149
1150	my $installsetpath = $followmeinfohash->{'finalinstalldir'};
1151
1152	installer::logger::include_header_into_logfile("Start: Signing installation set $installsetpath");
1153
1154	my $complete_success = 1;
1155	my $success = 1;
1156
1157	my $infoline = "Signing installation set in $installsetpath\n";
1158	push( @installer::globals::logfileinfo, $infoline);
1159
1160	# check required files.
1161	if ( ! $installer::globals::signfiles_checked ) { check_system_path(); }
1162
1163	# get cerficate information
1164	my $pw = get_pw($installer::globals::pwfile);
1165
1166	# making a copy of the installation set, if required
1167	if ( $make_copy ) { $installsetpath = copy_install_set($installsetpath); }
1168	else { $installsetpath = rename_install_set($installsetpath); }
1169
1170	# collecting all files in the installation set
1171	my ($allcabfiles, $allfiles, $msidatabase, $contains_external_cabfiles, $contains_msidatabase, $sourcefiles) = analyze_installset_content($installsetpath);
1172
1173	if ( $make_copy ) { compare_directories($installsetpath, $followmeinfohash->{'finalinstalldir'}, $sourcefiles); }
1174
1175	# changing into installation set
1176	my $from = cwd();
1177	my $fullmsidatabase = $installsetpath . $installer::globals::separator . $msidatabase;
1178
1179	if( $^O =~ /cygwin/i )
1180	{
1181		$fullmsidatabase = qx{cygpath -w "$fullmsidatabase"};
1182		$fullmsidatabase =~ s/\\/\\\\/g;
1183		$fullmsidatabase =~ s/\s*$//g;
1184	}
1185
1186	chdir($installsetpath);
1187
1188	if ( $contains_msidatabase )
1189	{
1190		# exclude media table from msi database and get all diskids.
1191		my ( $cabfilehash, $filenamehash, $lastsequencehash ) = collect_diskid_from_media_table($msidatabase, $followmeinfohash->{'languagestring'});
1192
1193		# Check, if there are internal cab files
1194		my ( $contains_internal_cabfiles, $all_internal_cab_files) = check_for_internal_cabfiles($cabfilehash);
1195
1196		if ( $contains_internal_cabfiles )
1197		{
1198			my $cabpath = get_cab_path($temppath);
1199			chdir($cabpath);
1200
1201			# Exclude all cabinet files from database
1202			$success = extract_cabs_from_database($fullmsidatabase, $all_internal_cab_files);
1203			if ( ! $success ) { $complete_success = 0; }
1204
1205			if ( $installer::globals::internal_cabinet_signing ) { sign_files_in_cabinet_files($followmeinfohash, $all_internal_cab_files, $pw, $temppath); }
1206
1207			$success = sign_files($followmeinfohash, $all_internal_cab_files, $pw, 0, $filenamehash, $lastsequencehash, $temppath);
1208			if ( ! $success ) { $complete_success = 0; }
1209			$success = msicert_database($fullmsidatabase, $all_internal_cab_files, $cabfilehash, 1);
1210			if ( ! $success ) { $complete_success = 0; }
1211
1212			# Include all cabinet files into database
1213			$success = include_cabs_into_database($fullmsidatabase, $all_internal_cab_files);
1214			if ( ! $success ) { $complete_success = 0; }
1215			chdir($installsetpath);
1216		}
1217
1218		# Warning: There might be a problem with very big cabinet files
1219		# signing all external cab files first
1220		if ( $contains_external_cabfiles )
1221		{
1222			if ( $installer::globals::internal_cabinet_signing ) { sign_files_in_cabinet_files($followmeinfohash, $allcabfiles, $pw, $temppath); }
1223
1224			$success = sign_files($followmeinfohash, $allcabfiles, $pw, 0, $filenamehash, $lastsequencehash, $temppath);
1225			if ( ! $success ) { $complete_success = 0; }
1226			$success = msicert_database($msidatabase, $allcabfiles, $cabfilehash, 0);
1227			if ( ! $success ) { $complete_success = 0; }
1228		}
1229	}
1230
1231	# finally all other files can be signed
1232	$success = sign_files($followmeinfohash, $allfiles, $pw, 0, 0, 0, $temppath);
1233	if ( ! $success ) { $complete_success = 0; }
1234
1235	# and changing back
1236	chdir($from);
1237
1238	installer::logger::include_header_into_logfile("End: Signing installation set $installsetpath");
1239
1240	return ($installsetpath);
1241}
1242
12431;
1244