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