Passed
Push — master ( 1adfd2...652786 )
by Stefan
06:27
created

WindowsCommon::sprintNsis()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * *****************************************************************************
4
 * Contributions to this work were made on behalf of the GÉANT project, a 
5
 * project that has received funding from the European Union’s Framework 
6
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
7
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
8
 * 691567 (GN4-1) and No. 731122 (GN4-2).
9
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
10
 * of the copyright in all material which was developed by a member of the GÉANT
11
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
12
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
13
 * UK as a branch of GÉANT Vereniging.
14
 * 
15
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
16
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
17
 *
18
 * License: see the web/copyright.inc.php file in the file structure or
19
 *          <base_url>/copyright.php after deploying the software
20
 */
21
22
/**
23
 * This file contains common functions needed by all Windows installers
24
 * @author Tomasz Wolniewicz <[email protected]>
25
 *
26
 * @package ModuleWriting
27
 */
28
29
namespace devices\ms;
30
use \Exception;
31
32
/**
33
 * This class defines common functions needed by all Windows installers
34
 * @author Tomasz Wolniewicz <[email protected]>
35
 *
36
 * @package ModuleWriting
37
 */
38
abstract class WindowsCommon extends \core\DeviceConfig {
39
40
    /**
41
     * copies various common files into temp dir for inclusion into installers
42
     * 
43
     * @return void
44
     * @throws Exception
45
     */
46
    public function copyBasicFiles() {
47
        if (!($this->copyFile('wlan_test.exe') &&
48
                $this->copyFile('check_wired.cmd') &&
49
                $this->copyFile('install_wired.cmd') &&
50
                $this->copyFile('cat_bg.bmp') &&
51
                $this->copyFile('base64.nsh'))) {
52
            throw new Exception("Copying needed files (part 1) failed for at least one file!");
53
        }
54
55
        if (!($this->copyFile('cat32.ico') &&
56
                $this->copyFile('cat_150.bmp') &&
57
                $this->copyFile('WLANSetEAPUserData/WLANSetEAPUserData32.exe', 'WLANSetEAPUserDatax86.exe') &&
58
                $this->copyFile('WLANSetEAPUserData/WLANSetEAPUserData64.exe', 'WLANSetEAPUserDatax64.exe'))) {
59
            throw new Exception("Copying needed files (part 2) failed for at least one file!");
60
        }
61
        if (!$this->translateFile('common.inc', 'common.nsh', $this->codePage)) {
62
            throw new Exception("Translating needed file common.inc failed!");
63
        }
64
        return;
65
    }
66
67
    /**
68
     * copies files relevant for EAP-pwd into installer temp directory for later inclusion into installers
69
     * 
70
     * @return void
71
     * @throws Exception
72
     */
73
    public function copyPwdFiles() {
74
        if (!($this->copyFile('Aruba_Networks_EAP-pwd_x32.msi') &&
75
                $this->copyFile('Aruba_Networks_EAP-pwd_x64.msi'))) {
76
            throw new Exception("Copying needed files (EAP-pwd) failed for at least one file!");
77
        }
78
        if (!$this->translateFile('pwd.inc', 'cat.NSI', $this->codePage)) {
79
            throw new Exception("Translating needed file pwd.inc failed!");
80
        }
81
    }
82
83
    /**
84
     * copies GEANTlink files into temp dir for later inclusion into installers
85
     * 
86
     * @return void
87
     * @throws Exception
88
     */
89
    public function copyGeantLinkFiles() {
90
        if (!($this->copyFile('GEANTLink/GEANTLink-x86.msi', 'GEANTLink-x86.msi') &&
91
                $this->copyFile('GEANTLink/GEANTLink-x64.msi', 'GEANTLink-x64.msi') &&
92
                $this->copyFile('GEANTLink/GEANTLink-ARM64.msi', 'GEANTLink-ARM64.msi') &&
93
                $this->copyFile('GEANTLink/CredWrite.exe', 'CredWrite.exe') &&
94
                $this->copyFile('GEANTLink/MsiUseFeature.exe', 'MsiUseFeature.exe'))) {
95
            throw new Exception("Copying needed files (GEANTLink) failed for at least one file!");
96
        }
97
        if (!$this->translateFile('geant_link.inc', 'cat.NSI', $this->codePage)) {
98
            throw new Exception("Translating needed file geant_link.inc failed!");
99
        }
100
    }
101
102
    /**
103
     * function to escape double quotes in a special NSI-compatible way
104
     * 
105
     * @param string $in input string
106
     * @return string
107
     */
108
    public static function echoNsis($in) {
109
        echo preg_replace('/"/', '$\"', $in);
110
    }
111
112
    /**
113
     * @param string $input input string
114
     * @return string
115
     */
116
    public static function sprintNsis($input) {
117
        return preg_replace('/"/', '$\"', $input);
118
    }
119
120
    /**
121
     * constructor. Figures out if GEANTlink is needed/wanted
122
     */
123
    public function __construct() {
124
        parent::__construct();
125
        $this->useGeantLink = (isset($this->options['args']) && $this->options['args'] == 'gl') ? 1 : 0;
126
    }
127
128
    /**
129
     * determine Windows codepage and language settings based on requested installer language
130
     * 
131
     * @return void
132
     */
133
    protected function prepareInstallerLang() {
134
        if (isset($this->LANGS[$this->languageInstance->getLang()])) {
135
            $language = $this->LANGS[$this->languageInstance->getLang()];
136
            $this->lang = $language['nsis'];
137
            $this->codePage = 'cp' . $language['cp'];
138
        } else {
139
            $this->lang = 'English';
140
            $this->codePage = 'cp1252';
141
        }
142
    }
143
144
    /**
145
     * creates HTML code which will be displayed when the "info" button is pressed
146
     * 
147
     * @return string the HTML code
148
     */
149
    public function writeDeviceInfo() {
150
        $ssids = $this->getAttribute('internal:SSID') ?? [];
151
        $ssidCount = count($ssids);
152
        $configList = CONFIG_CONFASSISTANT['CONSORTIUM']['ssid'] ?? [];
153
        $configCount = count($configList);
154
        $out = "<p>";
155
        $out .= sprintf(_("%s installer will be in the form of an EXE file. It will configure %s on your device, by creating wireless network profiles.<p>When you click the download button, the installer will be saved by your browser. Copy it to the machine you want to configure and execute."), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'], CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']);
156
        $out .= "<p>";
157
        if ($ssidCount > $configCount) {
158
            $out .= sprintf(ngettext("In addition to <strong>%s</strong> the installer will also configure access to:", "In addition to <strong>%s</strong> the installer will also configure access to the following networks:", $ssidCount - $configCount), implode(', ', $configList)) . " ";
159
            $out .= '<strong>' . join('</strong>, <strong>', array_diff(array_keys($ssids), $configList)) . '</strong>';
160
            $out .= "<p>";
161
        }
162
// TODO - change this below
163
        if ($this->selectedEapObject->isClientCertRequired()) {
164
            $out .= _("In order to connect to the network you will need an a personal certificate in the form of a p12 file. You should obtain this certificate from your organisation. Consult the support page to find out how this certificate can be obtained. Such certificate files are password protected. You should have both the file and the password available during the installation process.");
165
            return $out;
166
        }
167
        // not EAP-TLS
168
        $out .= _("In order to connect to the network you will need an account from your organisation. You should consult the support page to find out how this account can be obtained. It is very likely that your account is already activated.");
169
170
        if (!$this->useGeantLink && $this->selectedEap['OUTER'] == \core\common\EAP::TTLS) {
171
            $out .= "<p>";
172
            $out .= _("When you are connecting to the network for the first time, Windows will pop up a login box, where you should enter your user name and password. This information will be saved so that you will reconnect to the network automatically each time you are in the range.");
173
            if ($ssidCount > 1) {
174
                $out .= "<p>";
175
                $out .= _("You will be required to enter the same credentials for each of the configured networks:") . " ";
176
                $out .= '<strong>' . join('</strong>, <strong>', array_keys($ssids)) . '</strong>';
177
            }
178
        }
179
        return $out;
180
    }    
181
    
182
    /**
183
     * scales a logo to the desired size
184
     * @param string $imagePath path to the image
185
     * @param int    $maxSize   maximum size of output image (larger axis counts)
186
     * @return \IMagick IMagick image object
187
     */
188
    private function scaleLogo($imagePath, $maxSize) {
189
        $imageObject = new \Imagick($imagePath);
190
        $imageSize = $imageObject->getImageGeometry();
191
        $imageMax = max($imageSize);
192
        $this->loggerInstance->debug(5, "Logo size: ");
193
        $this->loggerInstance->debug(5, $imageSize);
194
        $this->loggerInstance->debug(5, "max=$imageMax\n");
195
// resize logo if necessary
196
        if ($imageMax > $maxSize) {
197
            if ($imageMax == $imageSize['width']) {
198
                $imageObject->scaleImage($maxSize, 0);
199
            } else {
200
                $imageObject->scaleImage(0, $maxSize);
201
            }
202
        }
203
        $imageSize = $imageObject->getImageGeometry();
204
        $this->background['freeHeight'] -= $imageSize['height'];
205
        return($imageObject);
206
    }
207
208
    /**
209
     * combines the inst and federation logo into one image and writes to file
210
     * 
211
     * @param array $logos   inst logo meta info
212
     * @param array $fedLogo fed logo meta info
213
     * @return void
214
     */
215
    protected function combineLogo($logos = NULL, $fedLogo = NULL) {
216
        // maximum size to which we want to resize the logos
217
        
218
        $maxSize = 120;
219
        // $freeTop is set to how much vertical space we need to leave at the top
220
        // this will depend on the design of the background
221
        $freeTop = 70;
222
        // $freeBottom is set to how much vertical space we need to leave at the bottom
223
        // this will depend on the design of the background
224
        $freeBottom = 30;
225
        
226
        $bgImage = new \Imagick(ROOT.'/devices/ms/Files/cat_bg.bmp');
227
        
228
        $bgImage->setFormat('BMP3');
229
        $bgImageSize = $bgImage->getImageGeometry();
230
        $logosToPlace = [];
231
        $this->background = [];
232
        $this->background['freeHeight'] = $bgImageSize['height'] - $freeTop - $freeBottom;
233
234
        if ($this->getAttribute('fed:include_logo_installers') === NULL) {
235
            $fedLogo = NULL;
236
        }
237
        if ($fedLogo != NULL) {
238
            $logosToPlace[] = $this->scaleLogo($fedLogo[0]['name'], $maxSize);
239
        }
240
        if ($logos != NULL) {
241
            $logosToPlace[] = $this->scaleLogo($logos[0]['name'], $maxSize);
242
        }
243
244
        $logoCount = count($logosToPlace);
245
        if ($logoCount > 0) {
246
            $voffset = $freeTop;
247
            $freeSpace = (int)round($this->background['freeHeight'] / ($logoCount + 1));
248
            foreach ($logosToPlace as $logo) {
249
                $voffset += $freeSpace;
250
                $logoSize = $logo->getImageGeometry();
251
                $hoffset = (int)round(($bgImageSize['width'] - $logoSize['width']) / 2);
252
                $bgImage->compositeImage($logo, $logo->getImageCompose(), $hoffset, $voffset);
253
                $voffset += $logoSize['height'];
254
                }
255
        }
256
//new image is saved as the background
257
        $bgImage->setImageFormat("BMP3");
258
        $fileHandle = fopen($this->FPATH."/tmp/cat_bg.bmp", "w");
259
        if ($fileHandle === FALSE) {
260
            throw new Exception("Unable to open the destination file for cat_bg.bmp");
261
        }
262
        $bgImage->writeImageFile($fileHandle);
263
        fclose($fileHandle);
264
    }
265
266
    /**
267
     * adds a digital signature to the installer, and returns path to file
268
     * 
269
     * @return string path to signed installer
270
     */
271
    protected function signInstaller() {
272
        $fileName = $this->installerBasename . '.exe';
273
        if (!$this->sign) {
274
            rename("installer.exe", $fileName);
275
            return $fileName;
276
        }
277
        // are actually signing
278
        $outputFromSigning = system($this->sign . " installer.exe '$fileName' > /dev/null");
279
        if ($outputFromSigning === FALSE) {
280
            $this->loggerInstance->debug(2, "Signing the WindowsCommon installer $fileName FAILED!\n");
281
        }
282
        return $fileName;
283
    }
284
285
    /**
286
     * creates one single installer .exe out of the NSH inputs and other files
287
     * 
288
     * @return void
289
     */
290
    protected function compileNSIS() {
291
        if (CONFIG_CONFASSISTANT['NSIS_VERSION'] >= 3) {
292
            $makensis = CONFIG_CONFASSISTANT['PATHS']['makensis'] . " -INPUTCHARSET UTF8";
293
        } else {
294
            $makensis = CONFIG_CONFASSISTANT['PATHS']['makensis'];
295
        }
296
        $lcAll = getenv("LC_ALL");
297
        putenv("LC_ALL=en_US.UTF-8");
298
        $command = $makensis . ' -V4 cat.NSI > nsis.log 2>&1';
299
        system($command);
300
        putenv("LC_ALL=" . $lcAll);
301
        $this->loggerInstance->debug(4, "compileNSIS:$command\n");
302
    }
303
304
    /**
305
     * find out where the user can get support
306
     * 
307
     * @param array  $attr list of profile attributes
308
     * @param string $type which type of support resource to we want
309
     * @return string NSH line with the resulting !define
310
     */
311
    private function getSupport($attr, $type) {
312
        $supportString = [
313
            'email' => 'SUPPORT',
314
            'url' => 'URL',
315
        ];
316
        $s = "support_" . $type . "_substitute";
317
        $substitute = $this->translateString($this->$s, $this->codePage);
318
        $returnValue = !empty($attr['support:' . $type][0]) ? $attr['support:' .  $type][0] : $substitute;
319
        return '!define ' . $supportString[$type] . ' "' . $returnValue . '"' . "\n";
320
    }
321
    
322
    /**
323
     * returns various NSH !define statements for later inclusion into master file
324
     * 
325
     * @param array $attr profile attributes
326
     * @return string
327
     */
328
    protected function writeNsisDefines($attr) {
329
        $fcontents = "\n" . '!define NSIS_MAJOR_VERSION ' . CONFIG_CONFASSISTANT['NSIS_VERSION'];
330
        if ($attr['internal:profile_count'][0] > 1) {
331
            $fcontents .= "\n" . '!define USER_GROUP "' . $this->translateString(str_replace('"', '$\\"', $attr['profile:name'][0]), $this->codePage) . '"
332
';
333
        }
334
        $fcontents .=  '
335
Caption "' . $this->translateString(sprintf(WindowsCommon::sprintNsis(_("%s installer for %s")), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'], $attr['general:instname'][0]), $this->codePage) . '"
336
!define APPLICATION "' . $this->translateString(sprintf(WindowsCommon::sprintNsis(_("%s installer for %s")), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'], $attr['general:instname'][0]), $this->codePage) . '"
337
!define VERSION "' . \core\CAT::VERSION_MAJOR . '.' . \core\CAT::VERSION_MINOR . '"
338
!define INSTALLER_NAME "installer.exe"
339
!define LANG "' . $this->lang . '"
340
!define LOCALE "' . preg_replace('/\..*$/', '', CONFIG['LANGUAGES'][$this->languageInstance->getLang()]['locale']) . '"
341
;--------------------------------
342
!define ORGANISATION "' . $this->translateString($attr['general:instname'][0], $this->codePage) . '"
343
';
344
        $fcontents .= $this->getSupport($attr, 'email');
345
        $fcontents .= $this->getSupport($attr, 'url');                
346
        if (\core\common\Entity::getAttributeValue($attr, 'media:wired', 0) == 'on') {
347
            $fcontents .= '!define WIRED
348
        ';
349
        }
350
        $fcontents .= '!define PROVIDERID "urn:UUID:' . $this->deviceUUID . '"
351
';
352
        if (!empty($attr['internal:realm'][0])) {
353
            $fcontents .= '!define REALM "' . $attr['internal:realm'][0] . '"
354
';
355
        }
356
        if(!empty($attr['internal:hint_userinput_suffix'][0]) && $attr['internal:hint_userinput_suffix'][0] == 1) {
357
            $fcontents .= '!define HINT_USER_INPUT "' . $attr['internal:hint_userinput_suffix'][0] . '"
358
';
359
        }
360
        if(!empty($attr['internal:verify_userinput_suffix'][0]) && $attr['internal:verify_userinput_suffix'][0] == 1) {
361
            $fcontents .= '!define VERIFY_USER_REALM_INPUT "' . $attr['internal:verify_userinput_suffix'][0] . '"
362
';
363
        }
364
        $fcontents .= $this->msInfoFile($attr);
365
        return $fcontents;
366
           
367
    }
368
    
369
    /**
370
     * includes NSH commands displaying terms of use file into installer, if any
371
     * 
372
     * @param array $attr profile attributes
373
     * @return string NSH commands
374
     * @throws Exception
375
     */
376
    protected function msInfoFile($attr) {
377
        $out = '';
378
        if (isset($attr['support:info_file'])) {
379
            $out .= '!define EXTERNAL_INFO "';
380
//  $this->loggerInstance->debug(4,"Info file type ".$attr['support:info_file'][0]['mime']."\n");
381
            if ($attr['internal:info_file'][0]['mime'] == 'rtf') {
382
                $out = '!define LICENSE_FILE "' . $attr['internal:info_file'][0]['name'];
383
            } elseif ($attr['internal:info_file'][0]['mime'] == 'txt') {
384
                $infoFile = file_get_contents($attr['internal:info_file'][0]['name']);
385
                if ($infoFile === FALSE) {
386
                    throw new Exception("We were told this file exists. Failing to read it is not really possible.");
387
                }
388
                if (CONFIG_CONFASSISTANT['NSIS_VERSION'] >= 3) {
389
                    $infoFileConverted = $infoFile;
390
                } else {
391
                    $infoFileConverted = iconv('UTF-8', $this->codePage . '//TRANSLIT', $infoFile);
392
                }
393
                if ($infoFileConverted !== FALSE && strlen($infoFileConverted) > 0) {
394
                    file_put_contents('info_f.txt', $infoFileConverted);
395
                    $out = '!define LICENSE_FILE " info_f.txt';
396
                }
397
            } else {
398
                $out = '!define EXTERNAL_INFO "' . $attr['internal:info_file'][0]['name'];
399
            }
400
401
            $out .= "\"\n";
402
        }
403
        $this->loggerInstance->debug(4, "Info file returned: $out");
404
        return $out;
405
    }
406
407
    /**
408
     * writes commands to delete SSIDs, if any, into a file
409
     * 
410
     * @param array $profiles WLAN profiles to delete
411
     * @return void
412
     * @throws Exception
413
     */
414
    protected function writeAdditionalDeletes($profiles) {
415
        if (count($profiles) == 0) {
416
            return;
417
        }
418
        $fileHandle = fopen('profiles.nsh', 'a');
419
        if ($fileHandle === FALSE) {
420
            throw new Exception("Unable to open possibly pre-existing profiles.nsh to append additional deletes.");
421
        }
422
        fwrite($fileHandle, "!define AdditionalDeletes\n");
423
        foreach ($profiles as $profile) {
424
            fwrite($fileHandle, "!insertmacro define_delete_profile \"$profile\"\n");
425
        }
426
        fclose($fileHandle);
427
    }
428
429
    /**
430
     * writes client certificate into file
431
     * 
432
     * @return void
433
     * @throws Exception
434
     */
435
    protected function writeClientP12File() {
436
        if (!is_array($this->clientCert)) {
437
            throw new Exception("the client block was called but there is no client certificate!");
438
        }
439
        file_put_contents('SB_cert.p12', $this->clientCert["certdata"]);
440
    }
441
442
    /**
443
     * nothing special to be done here
444
     * 
445
     * @return void
446
     */
447
    protected function writeTlsUserProfile() {
448
        
449
    }
450
451
    public $LANGS = [
452
        'fr' => ['nsis' => "French", 'cp' => '1252'],
453
        'de' => ['nsis' => "German", 'cp' => '1252'],
454
        'es' => ['nsis' => "SpanishInternational", 'cp' => '1252'],
455
        'it' => ['nsis' => "Italian", 'cp' => '1252'],
456
        'nl' => ['nsis' => "Dutch", 'cp' => '1252'],
457
        'sv' => ['nsis' => "Swedish", 'cp' => '1252'],
458
        'fi' => ['nsis' => "Finnish", 'cp' => '1252'],
459
        'pl' => ['nsis' => "Polish", 'cp' => '1250'],
460
        'ca' => ['nsis' => "Catalan", 'cp' => '1252'],
461
        'sr' => ['nsis' => "SerbianLatin", 'cp' => '1250'],
462
        'hr' => ['nsis' => "Croatian", 'cp' => '1250'],
463
        'sl' => ['nsis' => "Slovenian", 'cp' => '1250'],
464
        'da' => ['nsis' => "Danish", 'cp' => '1252'],
465
        'nb' => ['nsis' => "Norwegian", 'cp' => '1252'],
466
        'nn' => ['nsis' => "NorwegianNynorsk", 'cp' => '1252'],
467
        'el' => ['nsis' => "Greek", 'cp' => '1253'],
468
        'ru' => ['nsis' => "Russian", 'cp' => '1251'],
469
        'pt' => ['nsis' => "Portuguese", 'cp' => '1252'],
470
        'uk' => ['nsis' => "Ukrainian", 'cp' => '1251'],
471
        'cs' => ['nsis' => "Czech", 'cp' => '1250'],
472
        'sk' => ['nsis' => "Slovak", 'cp' => '1250'],
473
        'bg' => ['nsis' => "Bulgarian", 'cp' => '1251'],
474
        'hu' => ['nsis' => "Hungarian", 'cp' => '1250'],
475
        'ro' => ['nsis' => "Romanian", 'cp' => '1250'],
476
        'lv' => ['nsis' => "Latvian", 'cp' => '1257'],
477
        'mk' => ['nsis' => "Macedonian", 'cp' => '1251'],
478
        'et' => ['nsis' => "Estonian", 'cp' => '1257'],
479
        'tr' => ['nsis' => "Turkish", 'cp' => '1254'],
480
        'lt' => ['nsis' => "Lithuanian", 'cp' => '1257'],
481
        'ar' => ['nsis' => "Arabic", 'cp' => '1256'],
482
        'he' => ['nsis' => "Hebrew", 'cp' => '1255'],
483
        'id' => ['nsis' => "Indonesian", 'cp' => '1252'],
484
        'mn' => ['nsis' => "Mongolian", 'cp' => '1251'],
485
        'sq' => ['nsis' => "Albanian", 'cp' => '1252'],
486
        'br' => ['nsis' => "Breton", 'cp' => '1252'],
487
        'be' => ['nsis' => "Belarusian", 'cp' => '1251'],
488
        'is' => ['nsis' => "Icelandic", 'cp' => '1252'],
489
        'ms' => ['nsis' => "Malay", 'cp' => '1252'],
490
        'bs' => ['nsis' => "Bosnian", 'cp' => '1250'],
491
        'ga' => ['nsis' => "Irish", 'cp' => '1250'],
492
        'uz' => ['nsis' => "Uzbek", 'cp' => '1251'],
493
        'gl' => ['nsis' => "Galician", 'cp' => '1252'],
494
        'af' => ['nsis' => "Afrikaans", 'cp' => '1252'],
495
        'ast' => ['nsis' => "Asturian", 'cp' => '1252'],
496
    ];
497
    public $codePage;
498
    public $lang;
499
    public $useGeantLink;
500
    private $background;
501
502
}
503