1
#!/usr/bin/perl
2
#############################################################################
3
##
4
## Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
5
## All rights reserved.
6
## Contact: Nokia Corporation (qt-info@nokia.com)
7
##
8
## This file is part of the S60 port of the Qt Toolkit.
9
##
10
## $QT_BEGIN_LICENSE:LGPL$
11
## GNU Lesser General Public License Usage
12
## This file may be used under the terms of the GNU Lesser General Public
13
## License version 2.1 as published by the Free Software Foundation and
14
## appearing in the file LICENSE.LGPL included in the packaging of this
15
## file. Please review the following information to ensure the GNU Lesser
16
## General Public License version 2.1 requirements will be met:
17
## http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18
##
19
## In addition, as a special exception, Nokia gives you certain additional
20
## rights. These rights are described in the Nokia Qt LGPL Exception
21
## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22
##
23
## GNU General Public License Usage
24
## Alternatively, this file may be used under the terms of the GNU General
25
## Public License version 3.0 as published by the Free Software Foundation
26
## and appearing in the file LICENSE.GPL included in the packaging of this
27
## file. Please review the following information to ensure the GNU General
28
## Public License version 3.0 requirements will be met:
29
## http://www.gnu.org/copyleft/gpl.html.
30
##
31
## Other Usage
32
## Alternatively, this file may be used in accordance with the terms and
33
## conditions contained in a signed written agreement between you and Nokia.
34
##
35
##
36
##
37
##
38
##
39
## $QT_END_LICENSE$
40
##
41
#############################################################################
42
43
#######################################################################
44
#
45
# A script for setting binary capabilities based on .pkg file contents.
46
#
47
#######################################################################
48
49
#
50
# Note: Please make sure to output all changes done to the pkg file in a print statements
51
#       starting with "Patching: " to ease integration into IDEs!
52
#       Similarly, any actual error messages should start with "ERROR:"
53
#
54
55
use File::Copy;
56
use File::Spec;
57
use File::Path;
58
59
sub Usage() {
60
    print("This script can be used to set capabilities of all binaries\n");
61
    print("specified for deployment in a .pkg file.\n");
62
    print("If no capabilities are given, the binaries will be given the\n");
63
    print("capabilities supported by self-signed certificates.\n\n");
64
    print(" *** NOTE: If *_template.pkg file is given and one is using symbian-abld or\n");
65
    print(" symbian-sbsv2 platform, 'target-platform' is REQUIRED. ***\n\n");
66
    print(" *** NOTE2: When patching gcce binaries built with symbian-sbsv2 toolchain,\n");
67
    print(" armv5 must be specified as platform.\n");
68
    print("\nUsage: patch_capabilities.pl [-c|-t tmp_path] pkg_filename [target-platform [capability list]]\n");
69
    print("\nE.g. patch_capabilities.pl myapp_template.pkg release-armv5 \"All -TCB\"\n");
70
    print("\nThe parameter -c can be used to just check if package is compatible with self-signing\n");
71
    print("without actually doing any patching.\n");
72
    print("Explicit capability list cannot be used with -c parameter.\n");
73
    print("\nThe parameter -t can be used to specify a dir under which the temporary files are created.\n");
74
    print("Defaults to 'patch_capabilities_tmp' under the path to pkg file.\n");
75
    exit();
76
}
77
78
sub trim($) {
79
    my $string = shift;
80
    $string =~ s/^\s+//;
81
    $string =~ s/\s+$//;
82
    return $string;
83
}
84
85
my $epocroot = $ENV{EPOCROOT};
86
my $epocToolsDir = "";
87
if ($epocroot ne "") {
88
    $epocroot =~ s,\\,/,g;
89
    if ($epocroot =~ m,[^/]$,) {
90
        $epocroot = $epocroot."/";
91
    }
92
    $epocToolsDir = "${epocroot}epoc32/tools/";
93
}
94
95
my $nullDevice = "/dev/null";
96
$nullDevice = "NUL" if ($^O =~ /MSWin/);
97
98
my @capabilitiesToAllow = ("LocalServices", "NetworkServices", "ReadUserData", "UserEnvironment", "WriteUserData", "Location");
99
my @capabilitiesSpecified = ();
100
101
# If arguments were given to the script,
102
if (@ARGV)
103
{
104
    # Parse the first given script argument as a ".pkg" file name.
105
    my $pkgFileName = shift(@ARGV);
106
    my $justCheck = "";
107
    my $errorPrefix = "ERROR:";
108
    my $msgPrefix = "Patching:";
109
    my $tempPatchPath = "";
110
111
    if ($pkgFileName eq "-c") {
112
        $pkgFileName = shift(@ARGV);
113
        $justCheck = true;
114
        # All messages are simply warnings, as no actual patching is attempted.
115
        $msgPrefix = "Warning:";
116
        $errorPrefix = "Warning:";
117
    }
118
119
    if ($pkgFileName eq "-t") {
120
        $tempPatchPath = shift(@ARGV);
121
        $pkgFileName = shift(@ARGV);
122
    }
123
124
    my ($pkgVolume, $pkgPath, $pkgPlainFileName) = File::Spec->splitpath($pkgFileName);
125
    if ($tempPatchPath eq "") {
126
        $tempPatchPath = File::Spec->catpath($pkgVolume, $pkgPath."patch_capabilities_tmp", "");
127
    }
128
129
    mkpath($tempPatchPath);
130
131
    # These variables will only be set for template .pkg files.
132
    my $target;
133
    my $platform;
134
135
    # Check if using template .pkg and set target/platform variables
136
    if (($pkgFileName =~ m|_template\.pkg$|i) && -r($pkgFileName))
137
    {
138
        my $targetplatform;
139
        my $templateFile;
140
        my $templateContents;
141
        open($templateFile, "< $pkgFileName") or die ("Could not open $pkgFileName");
142
        $templateContents = <$templateFile>;
143
        close($templateFile);
144
        unless (($targetplatform = shift(@ARGV)) || $templateContents !~ /\$\(PLATFORM\)/)
145
        {
146
            Usage();
147
        }
148
        $targetplatform = "-" if (!$targetplatform);
149
        my @tmpvalues = split('-', $targetplatform);
150
        $target = $tmpvalues[0];
151
        $platform = $tmpvalues[1];
152
153
        # Convert visual target to real target (debug->udeb and release->urel)
154
        $target =~ s/debug/udeb/i;
155
        $target =~ s/release/urel/i;
156
157
        if (($platform =~ m/^gcce$/i) && ($ENV{SBS_HOME})) {
158
            # Print a informative note in case suspected misuse is detected.
159
            print "\nNote: You must use armv5 as platform when packaging gcce binaries built using symbian-sbsv2 mkspec.\n";
160
        }
161
    }
162
163
    # If the specified ".pkg" file exists (and can be read),
164
    if (($pkgFileName =~ m|\.pkg$|i) && -r($pkgFileName))
165
    {
166
        print ("\n");
167
        if ($justCheck) {
168
            print ("Checking");
169
        } else {
170
            print ("Patching");
171
        }
172
        print (" package file and relevant binaries...\n");
173
174
        if (!$justCheck) {
175
            # If there are more arguments given, parse them as capabilities.
176
            if (@ARGV)
177
            {
178
                @capabilitiesSpecified = ();
179
                while (@ARGV)
180
                {
181
                    push (@capabilitiesSpecified, pop(@ARGV));
182
                }
183
            }
184
        }
185
186
        # Start with no binaries listed.
187
        my @binaries = ();
188
        my $binariesDelimeter = "///";
189
190
        my $tempPkgFileName = $tempPatchPath."/__TEMP__".$pkgPlainFileName;
191
192
        if (!$justCheck) {
193
            unlink($tempPkgFileName);
194
            open (NEW_PKG, ">>".$tempPkgFileName);
195
        }
196
        open (PKG, "<".$pkgFileName);
197
198
        my $checkFailed = "";
199
        my $somethingPatched = "";
200
201
        # Parse each line.
202
        while (<PKG>)
203
        {
204
            my $line = $_;
205
            my $newLine = $line;
206
207
            # Patch pkg UID if it's in protected range
208
            if ($line =~ m/^\#.*\((0x[0-7][0-9a-fA-F]*)\).*$/)
209
            {
210
                my $oldUID = $1;
211
                print ("$msgPrefix UID $oldUID is not compatible with self-signing!\n");
212
213
                if ($justCheck) {
214
                    $checkFailed = true;
215
                } else {
216
                    my $newUID = $oldUID;
217
                    $newUID =~ s/0x./0xE/i;
218
                    $newLine =~ s/$oldUID/$newUID/;
219
                    print ("$msgPrefix Package UID changed to: $newUID.\n");
220
                    $somethingPatched = true;
221
                }
222
            }
223
224
            # If the line specifies a file, parse the source and destination locations.
225
            if ($line =~ m|^ *\"([^\"]+)\"\s*\-\s*\"([^\"]+)\"|)
226
            {
227
                my $sourcePath = $1;
228
229
                # If the given file is a binary, check the target and binary type (+ the actual filename) from its path.
230
                if ($sourcePath =~ m:\w+(\.dll|\.exe)$:i)
231
                {
232
                    # Do preprocessing for template pkg,
233
                    # In case of template pkg target and platform variables are set
234
                    if(length($target) && length($platform))
235
                    {
236
                        $sourcePath =~ s/\$\(PLATFORM\)/$platform/gm;
237
                        $sourcePath =~ s/\$\(TARGET\)/$target/gm;
238
                    }
239
240
                    my ($dummy1, $dummy2, $binaryBaseName) = File::Spec->splitpath($sourcePath);
241
242
                    if ($justCheck) {
243
                        push (@binaries, $binaryBaseName.$binariesDelimeter.$sourcePath);
244
                    } else {
245
                        # Copy original files over to patching dir
246
                        # Patching dir will be flat to make it cleanable with QMAKE_CLEAN, so path
247
                        # will be collapsed into the file name to avoid name collisions in the rare
248
                        # case where custom pkg rules are used to install files with same names from
249
                        # different directories (probably using platform checks to choose only one of them.)
250
                        my $patchedSourcePath = $sourcePath;
251
                        $patchedSourcePath =~ s/[\/\\:]/_/g;
252
                        $patchedSourcePath = "$tempPatchPath/$patchedSourcePath";
253
                        $newLine =~ s/^.*(\.dll|\.exe)(.*)(\.dll|\.exe)/\"$patchedSourcePath$2$3/i;
254
255
                        copy($sourcePath, $patchedSourcePath) or die "$sourcePath cannot be copied for patching.";
256
                        push (@binaries, $binaryBaseName.$binariesDelimeter.$patchedSourcePath);
257
                    }
258
                }
259
            }
260
261
            print NEW_PKG $newLine;
262
263
            chomp ($line);
264
        }
265
266
        close (PKG);
267
        if (!$justCheck) {
268
            close (NEW_PKG);
269
270
            unlink($pkgFileName);
271
            rename($tempPkgFileName, $pkgFileName);
272
        }
273
        print ("\n");
274
275
        my $baseCommandToExecute = "${epocToolsDir}elftran -vid 0x0 -capability \"%s\" ";
276
277
        # Actually set the capabilities of the listed binaries.
278
        foreach my $binariesItem(@binaries)
279
        {
280
            $binariesItem =~ m|^(.*)$binariesDelimeter(.*)$|;
281
            my $binaryBaseName = $1;
282
            my $binaryPath = $2;
283
284
            # Create the command line for setting the capabilities.
285
            my $commandToExecute = $baseCommandToExecute;
286
            my $executeNeeded = "";
287
            if (@capabilitiesSpecified)
288
            {
289
                $commandToExecute = sprintf($baseCommandToExecute, join(" ", @capabilitiesSpecified));
290
                $executeNeeded = true;
291
                my $capString = join(" ", @capabilitiesSpecified);
292
                print ("$msgPrefix Patching the the Vendor ID to 0 and the capabilities used to: \"$capString\" in \"$binaryBaseName\".\n");
293
            } else {
294
                # Test which capabilities are present and then restrict them to the allowed set.
295
                # This avoid raising the capabilities of apps that already have none.
296
                my $dllCaps;
297
                open($dllCaps, "${epocToolsDir}elftran -dump s $binaryPath |") or die ("ERROR: Could not execute elftran");
298
                my $capsFound = 0;
299
                my $originalVid;
300
                my @capabilitiesToSet;
301
                my $capabilitiesToAllow = join(" ", @capabilitiesToAllow);
302
                my @capabilitiesToDrop;
303
                while (<$dllCaps>) {
304
                    if (/^Secure ID: ([0-7][0-9a-fA-F]*)$/) {
305
                        my $exeSid = $1;
306
                        if ($binaryBaseName =~ /\.exe$/) {
307
                            # Installer refuses to install protected executables in a self signed package, so abort if one is detected.
308
                            # We can't simply just patch the executable SID, as any registration resources executable uses will be linked to it via SID.
309
                            print ("$errorPrefix Executable with SID in the protected range (0x$exeSid) detected: \"$binaryBaseName\". A self-signed sis with protected executables is not supported.\n\n");
310
                            $checkFailed = true;
311
                        }
312
                    }
313
                    if (/^Vendor ID: ([0-9a-fA-F]*)$/) {
314
                        $originalVid = "$1";
315
                    }
316
                    if (!$capsFound) {
317
                        $capsFound = 1 if (/Capabilities:/);
318
                    } else {
319
                        $_ = trim($_);
320
                        if ($capabilitiesToAllow =~ /$_/) {
321
                            push(@capabilitiesToSet, $_);
322
                        } else {
323
                            push(@capabilitiesToDrop, $_);
324
                        }
325
                    }
326
                }
327
                close($dllCaps);
328
                if ($originalVid !~ "00000000") {
329
                    print ("$msgPrefix Non-zero vendor ID (0x$originalVid) is incompatible with self-signed packages in \"$binaryBaseName\"");
330
                    if ($justCheck) {
331
                        print (".\n\n");
332
                        $checkFailed = true;
333
                    } else {
334
                        print (", setting it to zero.\n\n");
335
                        $executeNeeded = true;
336
                    }
337
                }
338
                if ($#capabilitiesToDrop) {
339
                    my $capsToDropStr = join("\", \"", @capabilitiesToDrop);
340
                    $capsToDropStr =~ s/\", \"$//;
341
342
                    if ($justCheck) {
343
                        print ("$msgPrefix The following capabilities used in \"$binaryBaseName\" are not compatible with a self-signed package: \"$capsToDropStr\".\n\n");
344
                        $checkFailed = true;
345
                    } else {
346
                        if ($binaryBaseName =~ /\.exe$/) {
347
                            # While libraries often have capabilities they do not themselves need just to enable them to be loaded by wider variety of processes,
348
                            # executables are more likely to need every capability they have been assigned or they won't function correctly.
349
                            print ("$errorPrefix Executable with capabilities incompatible with self-signing detected: \"$binaryBaseName\". (Incompatible capabilities: \"$capsToDropStr\".) Reducing capabilities is only supported for libraries.\n");
350
                            $checkFailed = true;
351
                        } else {
352
                            print ("$msgPrefix The following capabilities used in \"$binaryBaseName\" are not compatible with a self-signed package and will be removed: \"$capsToDropStr\".\n");
353
                            $executeNeeded = true;
354
                        }
355
                    }
356
                }
357
                $commandToExecute = sprintf($baseCommandToExecute, join(" ", @capabilitiesToSet));
358
            }
359
            $commandToExecute .= $binaryPath;
360
361
            if ($executeNeeded) {
362
                # Actually execute the elftran command to set the capabilities.
363
                print ("\n");
364
                system ("$commandToExecute > $nullDevice");
365
                $somethingPatched = true;
366
            }
367
        }
368
369
        if ($checkFailed) {
370
            print ("\n");
371
            if ($justCheck) {
372
                print ("$msgPrefix The package is not compatible with self-signing. ");
373
            } else {
374
                print ("$errorPrefix Unable to patch the package for self-singing. ");
375
            }
376
            print ("Use a proper developer certificate for signing this package.\n\n");
377
            exit(1);
378
        }
379
380
        if ($justCheck) {
381
            print ("Package is compatible with self-signing.\n");
382
        } else {
383
            if ($somethingPatched) {
384
                print ("NOTE: A patched package may not work as expected due to reduced capabilities and other modifications,\n");
385
                print ("      so it should not be used for any kind of Symbian signing or distribution!\n");
386
                print ("      Use a proper certificate to avoid the need to patch the package.\n");
387
            } else {
388
                print ("No patching was required!\n");
389
            }
390
        }
391
        print ("\n");
392
    } else {
393
        Usage();
394
    }
395
}
396
else
397
{
398
    Usage();
399
}