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