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