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