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