Passed
Push — master ( 50c81e...ac314a )
by Stefan
06:50
created

SanityTests::testUDPhosts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 0
dl 0
loc 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 56 and the first side effect is on line 52.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
/**
24
 * 
25
 * 
26
 * This is the definition of the CAT class implementing various configuration
27
 * tests. 
28
 * Each test is implemented as a priviate method which needs to be named "test_name_test".
29
 * The test returns the results by calling the testReturn method, this passing the return
30
 * code and the explanatory message. Multiple calls to testReturn are allowed.
31
 *
32
 * An individual test can be run by the "test" method which takes the test name as an argument
33
 * multiple tests should be run by the run_all_tests method which takes an array as an argument
34
 * see method descriptions for more information.
35
 * 
36
 * The results of the tests are passed within the $test_result array
37
 *
38
 * Some configuration of this class is required, see further down.
39
 * @author Stefan Winter <[email protected]>
40
 * @author Tomasz Wolniewicz <[email protected]>
41
 *
42
 * @license see LICENSE file in root directory
43
 *
44
 * @package Utilities
45
 */
46
47
namespace core;
48
49
use GeoIp2\Database\Reader;
50
use \Exception;
51
52
require_once dirname(dirname(__FILE__)) . "/config/_config.php";
53
require_once dirname(dirname(__FILE__)) . "/core/PHPMailer/src/PHPMailer.php";
54
require_once dirname(dirname(__FILE__)) . "/core/PHPMailer/src/SMTP.php";
55
56
class SanityTests extends CAT {
57
    /* in this section set current CAT requirements */
58
59
    /**
60
     * the minumum required php version 
61
     * 
62
     * @var string
63
     */
64
    private $php_needversion = '7.2.0';
65
66
    /**
67
     * the minimum required simpleSAMLphp version
68
     * 
69
     * @var array
70
     */
71
    private $ssp_needversion = ['major' => 1, 'minor' => 15];
72
73
    /**
74
     * all required NSIS modules
75
     * 
76
     * @var array<string>
77
     */
78
    private $NSIS_Modules = [
79
        "nsArray.nsh",
80
        "FileFunc.nsh",
81
        "LogicLib.nsh",
82
        "WordFunc.nsh",
83
        "FileFunc.nsh",
84
        "x64.nsh",
85
    ];
86
87
    /**
88
     * set $profile_option_ct to the number of rows returned by 
89
     * "SELECT * FROM profile_option_dict" 
90
     * to compare actual vs. expected database structure
91
     * 
92
     * @var integer
93
     */
94
    private $profile_option_ct;
95
96
    /**
97
     * set $view_admin_ct to the number of rows returned by "desc view_admin" 
98
     *
99
     * @var integer
100
     */
101
    private $view_admin_ct = 8;
102
103
    /* end of config */
104
105
    /**
106
     * array holding the output of all tests that were executed
107
     * 
108
     * @var array
109
     */
110
    public $out;
111
112
    /**
113
     * temporary storage for the name of the test as it is being run
114
     * 
115
     * @var string
116
     */
117
    public $name;
118
119
    /**
120
     * initialise the tests. Includes counting the number of expected rows in the profile_option_dict table.
121
     */
122
    public function __construct() {
123
        parent::__construct();
124
        $this->test_result = [];
125
        $this->test_result['global'] = 0;
126
        // parse the schema file to find out the number of expected rows...
127
        $schema = file(dirname(dirname(__FILE__)) . "/schema/schema.sql");
128
        $this->profile_option_ct = 0;
129
        $passedTheWindmill = FALSE;
130
        foreach ($schema as $schemaLine) {
131
            if (preg_match("/^INSERT INTO \`profile_option_dict\` VALUES/", $schemaLine)) {
132
                $passedTheWindmill = TRUE;
133
                continue;
134
            }
135
            if ($passedTheWindmill) {
136
                if (substr($schemaLine, 0, 1) == '(') { // a relevant line in schema
137
                    $this->profile_option_ct = $this->profile_option_ct + 1;
138
                } else { // anything else, quit parsing
139
                    break;
140
                }
141
            }
142
        }
143
    }
144
145
    /**
146
     * The single test wrapper
147
     * @param string $test the test name
148
     * @return void
149
     */
150
    public function runTest($test) {
151
        $this->out[$test] = [];
152
        $this->name = $test;
153
        $m_name = 'test' . $test;
154
        $this->test_result[$test] = 0;
155
        if (!method_exists($this, $m_name)) {
156
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Configuration error, no test configured for <strong>$test</strong>.");
157
            return;
158
        }
159
        $this->$m_name();
160
    }
161
162
    /**
163
     * The multiple tests wrapper
164
     * @param array $Tests the tests array is a simple string array, where each 
165
     *                     entry is a test name. The test names can also be 
166
     *                     given in the format "test=>subtest", which defines a
167
     *                     conditional execution of the "subtest" if the "test"
168
     *                     was run earlier and returned a success.
169
     * @return void
170
     */
171
    public function runTests($Tests) {
172
        foreach ($Tests as $testName) {
173
            $matchArray = [];
174
            if (preg_match('/(.+)=>(.+)/', $testName, $matchArray)) {
175
                $tst = $matchArray[1];
176
                $subtst = $matchArray[2];
177
                if ($this->test_result[$tst] < \core\common\Entity::L_ERROR) {
178
                    $this->runTest($subtst);
179
                }
180
            } else {
181
                $this->runTest($testName);
182
            }
183
        }
184
    }
185
186
    /**
187
     * enumerates the tests which are defined
188
     * 
189
     * @return array
190
     */
191
    public function getTestNames() {
192
        $T = get_class_methods($this);
193
        $out = [];
194
        foreach ($T as $t) {
195
            if (preg_match('/^test(.*)$/', $t, $m)) {
196
                $out[] = $m[1];
197
            }
198
        }
199
        return $out;
200
    }
201
202
    /**
203
     * This array is used to return the test results.
204
     * As the 'global' entry it returns the maximum return value
205
     * from all tests.
206
     * Individual tests results are teturned as separate entires
207
     * indexed by test names; each value is an array passing "level" and "message"
208
     * from each of the tests.
209
     * $test_result is set by the testReturn method
210
     *
211
     * @var array $test_result
212
     */
213
    public $test_result;
214
215
    /**
216
     * stores the result of a given test in standardised format
217
     * 
218
     * @param int    $level   severity level of the result
219
     * @param string $message verbal description of the result
220
     * @return void
221
     */
222
    private function storeTestResult($level, $message) {
223
        $this->out[$this->name][] = ['level' => $level, 'message' => $message];
224
        $this->test_result[$this->name] = max($this->test_result[$this->name], $level);
225
        $this->test_result['global'] = max($this->test_result['global'], $level);
226
    }
227
228
    /**
229
     * finds out if a path name is configured as an absolute path or only implicit (e.g. is in $PATH)
230
     * @param string $pathToCheck the path to check
231
     * @return array
232
     */
233
    private function getExecPath($pathToCheck) {
234
        $the_path = "";
235
        $exec_is = "UNDEFINED";
236
        foreach ([CONFIG, CONFIG_CONFASSISTANT, CONFIG_DIAGNOSTICS] as $config) {
237
            if (!empty($config['PATHS'][$pathToCheck])) {
238
                $matchArray = [];
239
                preg_match('/([^ ]+) ?/', $config['PATHS'][$pathToCheck], $matchArray);
240
                $exe = $matchArray[1];
241
                $the_path = exec("which " . $config['PATHS'][$pathToCheck]);
242
                if ($the_path == $exe) {
243
                    $exec_is = "EXPLICIT";
244
                } else {
245
                    $exec_is = "IMPLICIT";
246
                }
247
                return(['exec' => $the_path, 'exec_is' => $exec_is]);
248
            }
249
        }
250
        return(['exec' => $the_path, 'exec_is' => $exec_is]);
251
    }
252
253
    /**
254
     *  Test for php version
255
     * 
256
     * @return void
257
     */
258
    private function testPhp() {
259
        if (version_compare(phpversion(), $this->php_needversion, '>=')) {
260
            $this->storeTestResult(\core\common\Entity::L_OK, "<strong>PHP</strong> is sufficiently recent. You are running " . phpversion() . ".");
261
        } else {
262
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>PHP</strong> is too old. We need at least $this->php_needversion, but you only have " . phpversion() . ".");
263
        }
264
    }
265
266
    /**
267
     * set for cat_base_url setting
268
     * 
269
     * @return void
270
     */
271
    private function testCatBaseUrl() {
272
        $rootUrl = substr(CONFIG['PATHS']['cat_base_url'], -1) === '/' ? substr(CONFIG['PATHS']['cat_base_url'], 0, -1) : CONFIG['PATHS']['cat_base_url'];
273
        preg_match('/(^.*)\/admin\/112365365321.php/', $_SERVER['SCRIPT_NAME'], $m);
274
        if ($rootUrl === $m[1]) {
275
            $this->storeTestResult(\core\common\Entity::L_OK, "<strong>cat_base_url</strong> set correctly");
276
        } else {
277
            $rootFromScript = $m[1] === '' ? '/' : $m[1];
278
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>cat_base_url</strong> is set to <strong>" . CONFIG['PATHS']['cat_base_url'] . "</strong> and should be <strong>$rootFromScript</strong>");
279
        }
280
    }
281
282
    /**
283
     * check whether the configured RADIUS hosts actually exist
284
     * 
285
     * @return void
286
     */
287
    private function testRADIUSProbes() {
288
        $probeReturns = [];
289
        foreach (CONFIG_DIAGNOSTICS['RADIUSTESTS']['UDP-hosts'] as $oneProbe) {
290
            $statusServer = new diag\RFC5997Tests($oneProbe['ip'], 1812, $oneProbe['secret']);
291
            if ($statusServer->statusServerCheck() !== diag\AbstractTest::RETVAL_OK) {
292
                $probeReturns[] = $oneProbe['display_name'];
293
            }
294
        }
295
        if (count($probeReturns) == 0) {
296
            $this->storeTestResult(common\Entity::L_OK, "All configured RADIUS/UDP probes are reachable.");
297
        } else {
298
            $this->storeTestResult(common\Entity::L_ERROR, "The following RADIUS probes are NOT reachable: ".implode(', ',$probeReturns));
299
        }
300
    }
301
302
    /**
303
     * test for simpleSAMLphp
304
     * 
305
     * @return void
306
     */
307
    private function testSsp() {
308
        if (!is_file(CONFIG['AUTHENTICATION']['ssp-path-to-autoloader'])) {
309
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>simpleSAMLphp</strong> not found!");
310
        } else {
311
            include_once CONFIG['AUTHENTICATION']['ssp-path-to-autoloader'];
312
            $SSPconfig = \SimpleSAML_Configuration::getInstance();
313
            $sspVersion = explode('.', $SSPconfig->getVersion());
314
            if ((int) $sspVersion[0] >= $this->ssp_needversion['major'] && (int) $sspVersion[1] >= $this->ssp_needversion['minor']) {
315
                $this->storeTestResult(\core\common\Entity::L_OK, "<strong>simpleSAMLphp</strong> is sufficently recent. You are running " . implode('.', $sspVersion));
316
            } else {
317
                $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>simpleSAMLphp</strong> is too old. We need at least " . implode('.', $this->ssp_needversion));
318
            }
319
        }
320
    }
321
322
    /**
323
     * test for security setting
324
     * 
325
     * @return void
326
     */
327
    private function testSecurity() {
328
        if (in_array("I do not care about security!", CONFIG['SUPERADMINS'])) {
329
            $this->storeTestResult(\core\common\Entity::L_WARN, "You do not care about security. This page should be made accessible to the CAT admin only! See config-master.php: 'SUPERADMINS'!");
330
        }
331
    }
332
333
    /**
334
     * test if zip is available
335
     * 
336
     * @return void
337
     */
338
    private function testZip() {
339
        if (exec("which zip") != "") {
340
            $this->storeTestResult(\core\common\Entity::L_OK, "<strong>zip</strong> binary found.");
341
        } else {
342
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>zip</strong> not found in your \$PATH!");
343
        }
344
    }
345
346
    /**
347
     * test if eapol_test is available and recent enough
348
     * 
349
     * @return void
350
     */
351
    private function testEapoltest() {
352
        exec(CONFIG_DIAGNOSTICS['PATHS']['eapol_test'], $out, $retval);
353
        if ($retval == 255) {
354
            $o = preg_grep('/-o<server cert/', $out);
355
            if (count($o) > 0) {
356
                $this->storeTestResult(\core\common\Entity::L_OK, "<strong>eapol_test</strong> script found.");
357
            } else {
358
                $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>eapol_test</strong> found, but is too old!");
359
            }
360
        } else {
361
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>eapol_test</strong> not found!");
362
        }
363
    }
364
365
    /**
366
     * test if logdir exists and is writable
367
     * 
368
     * @return void
369
     */
370
    private function testLogdir() {
371
        if (fopen(CONFIG['PATHS']['logdir'] . "/debug.log", "a") == FALSE) {
372
            $this->storeTestResult(\core\common\Entity::L_WARN, "Log files in <strong>" . CONFIG['PATHS']['logdir'] . "</strong> are not writable!");
373
        } else {
374
            $this->storeTestResult(\core\common\Entity::L_OK, "Log directory is writable.");
375
        }
376
    }
377
378
    /**
379
     * test for required PHP modules
380
     * 
381
     * @return void
382
     */
383
    private function testPhpModules() {
384
        if (function_exists('idn_to_ascii')) {
385
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP can handle internationalisation.");
386
        } else {
387
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP can <strong>NOT</strong> handle internationalisation (idn_to_ascii() from php7.0-intl).");
388
        }
389
390
        if (function_exists('gettext')) {
391
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>GNU Gettext</strong> is installed.");
392
        } else {
393
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GNU Gettext</strong> not found!");
394
        }
395
396
        if (function_exists('openssl_sign')) {
397
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>OpenSSL</strong> is installed.");
398
        } else {
399
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>OpenSSL</strong> not found!");
400
        }
401
402
        if (class_exists('\Imagick')) {
403
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>Imagick</strong> is installed.");
404
        } else {
405
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>Imagick</strong> not found! Get it from your distribution or <a href='http://pecl.php.net/package/imagick'>here</a>.");
406
        }
407
408
        if (function_exists('ImageCreate')) {
409
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>GD</strong> is installed.");
410
        } else {
411
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GD</strong> not found!</a>.");
412
        }
413
414
        if (function_exists('mysqli_connect')) {
415
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>MySQL</strong> is installed.");
416
        } else {
417
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>MySQL</strong> not found!");
418
        }
419
    }
420
421
    /**
422
     * test if GeoIP is installed correctly
423
     * 
424
     * @return void
425
     */
426
    private function testGeoip() {
427
        $host_4 = '145.0.2.50';
428
        $host_6 = '2001:610:188:444::50';
429
        switch (CONFIG['GEOIP']['version']) {
430
            case 0:
431
                $this->storeTestResult(\core\common\Entity::L_REMARK, "As set in the config, no geolocation service will be used");
432
                break;
433
            case 1:
434
                if (!function_exists('geoip_record_by_name')) {
435
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GeoIP</strong> (legacy) not found! Get it from your distribution or <a href='http://pecl.php.net/package/geoip'>here</a> or better install GeoIP2 from <a href='https://github.com/maxmind/GeoIP2-php'>here</a>.");
436
                    return;
437
                }
438
                $record = geoip_record_by_name($host_4);
439
                if ($record === FALSE) {
440
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GeoIP</strong> (legacy) found but not working properly, perhaps you need to download the databases. See utils/GeoIP-update.sh in the CAT distribution and use it tu update the GeoIP database regularly.");
441
                    return;
442
                }
443
                if ($record['city'] != 'Utrecht') {
444
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GeoIP</strong> (legacy) found but not working properly, perhaps you need to download the databases. See utils/GeoIP-update.sh in the CAT distribution and use it tu update the GeoIP database regularly.");
445
                    return;
446
                }
447
                $this->storeTestResult(\core\common\Entity::L_REMARK, "PHP extension <strong>GeoIP</strong> (legacy) is installed and working. See utils/GeoIP-update.sh in the CAT distribution and use it tu update the GeoIP database regularly. We stronly advise to replace the legacy GeoIP with GeoIP2 from <a href='https://github.com/maxmind/GeoIP2-php'>here</a>.");
448
                break;
449
            case 2:
450
                if (!is_file(CONFIG['GEOIP']['geoip2-path-to-autoloader'])) {
451
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GeoIP2</strong> not found! Get it from <a href='https://github.com/maxmind/GeoIP2-php'>here</a>.");
452
                    return;
453
                }
454
                if (!is_file(CONFIG['GEOIP']['geoip2-path-to-db'])) {
455
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>GeoIP2 database</strong> not found! See utils/GeoIP-update.sh in the CAT distribution and use it tu update the GeoIP database regularly.");
456
                    return;
457
                }
458
                include_once CONFIG['GEOIP']['geoip2-path-to-autoloader'];
459
                $reader = new Reader(CONFIG['GEOIP']['geoip2-path-to-db']);
460
                try {
461
                    $record = $reader->city($host_4);
462
                } catch (Exception $e) {
463
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GeoIP2</strong> found but not working properly, perhaps you need to download the databases. See utils/GeoIP-update.sh in the CAT distribution and use it to update the GeoIP database regularly.");
464
                    return;
465
                }
466
                if ($record->city->name != 'Utrecht') {
467
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GeoIP2</strong> found but not working properly, perhaps you need to download the databases. See utils/GeoIP-update.sh in the CAT distribution and use it to update the GeoIP database regularly.");
468
                    return;
469
                }
470
                try {
471
                    $record = $reader->city($host_6);
472
                } catch (Exception $e) {
473
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GeoIP2</strong> found but not working properly with IPv6, perhaps you need to download the databases. See utils/GeoIP-update.sh in the CAT distribution and use it tu update the GeoIP database regularly.");
474
                    return;
475
                }
476
                if ($record->city->name != 'Utrecht') {
477
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GeoIP2</strong> found but not working properly with IPv6, perhaps you need to download the databases. See utils/GeoIP-update.sh in the CAT distribution and use it tu update the GeoIP database regularly.");
478
                    return;
479
                }
480
                $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>GeoIP2</strong> is installed and working. See utils/GeoIP-update.sh in the CAT distribution and use it tu update the GeoIP database regularly.");
481
                break;
482
            default:
483
                $this->storeTestResult(\core\common\Entity::L_ERROR, 'Check CONFIG[\'GEOIP\'][\'version\'], it must be set to either 1 or 2');
484
                break;
485
        }
486
    }
487
488
    /**
489
     * test if openssl is available
490
     * 
491
     * @return void
492
     */
493
    private function testOpenssl() {
494
        $A = $this->getExecPath('openssl');
495
        if ($A['exec'] != "") {
496
            $t = exec($A['exec'] . ' version');
497
            if ($A['exec_is'] == "EXPLICIT") {
498
                $this->storeTestResult(\core\common\Entity::L_OK, "<strong>$t</strong> was found and is configured explicitly in your config.");
499
            } else {
500
                $this->storeTestResult(\core\common\Entity::L_WARN, "<strong>$t</strong> was found, but is not configured with an absolute path in your config.");
501
            }
502
        } else {
503
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>openssl</strong> was not found on your system!");
504
        }
505
    }
506
507
    /**
508
     * test if makensis is available
509
     * 
510
     * @return void
511
     */
512
    private function testMakensis() {
513
        if (!is_numeric(CONFIG_CONFASSISTANT['NSIS_VERSION'])) {
514
            $this->storeTestResult(\core\common\Entity::L_ERROR, "NSIS_VERSION needs to be numeric!");
515
            return;
516
        }
517
        if (CONFIG_CONFASSISTANT['NSIS_VERSION'] < 2) {
518
            $this->storeTestResult(\core\common\Entity::L_ERROR, "NSIS_VERSION needs to be at least 2!");
519
            return;
520
        }
521
        $A = $this->getExecPath('makensis');
522
        if ($A['exec'] != "") {
523
            $t = exec($A['exec'] . ' -VERSION');
524
            if ($A['exec_is'] == "EXPLICIT") {
525
                $this->storeTestResult(\core\common\Entity::L_OK, "<strong>makensis $t</strong> was found and is configured explicitly in your config.");
526
            } else {
527
                $this->storeTestResult(\core\common\Entity::L_WARN, "<strong>makensis $t</strong> was found, but is not configured with an absolute path in your config.");
528
            }
529
            $outputArray = [];
530
            exec($A['exec'] . ' -HELP', $outputArray);
531
            $t1 = count(preg_grep('/INPUTCHARSET/', $outputArray));
532
            if ($t1 == 1 && CONFIG_CONFASSISTANT['NSIS_VERSION'] == 2) {
533
                $this->storeTestResult(\core\common\Entity::L_ERROR, "Declared NSIS_VERSION does not seem to match the file pointed to by PATHS['makensis']!");
534
            }
535
            if ($t1 == 0 && CONFIG_CONFASSISTANT['NSIS_VERSION'] >= 3) {
536
                $this->storeTestResult(\core\common\Entity::L_ERROR, "Declared NSIS_VERSION does not seem to match the file pointed to by PATHS['makensis']!");
537
            }
538
        } else {
539
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>makensis</strong> was not found on your system!");
540
        }
541
    }
542
543
    /**
544
     * test if all required NSIS modules are available
545
     * 
546
     * @return void
547
     */
548
    private function testNSISmodules() {
549
        $tmp_dir = \core\common\Entity::createTemporaryDirectory('installer', 0)['dir'];
550
        if (!chdir($tmp_dir)) {
551
            $this->loggerInstance->debug(2, "Cannot chdir to $tmp_dir\n");
552
            $this->storeTestResult(\core\common\Entity::L_ERROR, "NSIS modules test - problem with temporary directory permissions, cannot continue");
553
            return;
554
        }
555
        $exe = 'tt.exe';
556
        $NSIS_Module_status = [];
557
        foreach ($this->NSIS_Modules as $module) {
558
            unset($out);
559
            exec(CONFIG_CONFASSISTANT['PATHS']['makensis'] . " -V1 '-X!include $module' '-XOutFile $exe' '-XSection X' '-XSectionEnd'", $out, $retval);
560
            if ($retval > 0) {
561
                $NSIS_Module_status[$module] = 0;
562
            } else {
563
                $NSIS_Module_status[$module] = 1;
564
            }
565
        }
566
        if (is_file($exe)) {
567
            unlink($exe);
568
        }
569
        foreach ($NSIS_Module_status as $module => $status) {
570
            if ($status == 1) {
571
                $this->storeTestResult(\core\common\Entity::L_OK, "NSIS module <strong>$module</strong> was found.");
572
            } else {
573
                $this->storeTestResult(\core\common\Entity::L_ERROR, "NSIS module <strong>$module</strong> was not found or is not working correctly.");
574
            }
575
        }
576
    }
577
578
    /**
579
     * test access to dowloads directories
580
     * 
581
     * @return void
582
     */
583
    private function testDirectories() {
584
        $Dir1 = \core\common\Entity::createTemporaryDirectory('installer', 0);
585
        $dir1 = $Dir1['dir'];
586
        $base1 = $Dir1['base'];
587
        if ($dir1) {
588
            $this->storeTestResult(\core\common\Entity::L_OK, "Installer cache directory is writable.");
589
            \core\common\Entity::rrmdir($dir1);
590
        } else {
591
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Installer cache directory $base1 does not exist or is not writable!");
592
        }
593
        $Dir2 = \core\common\Entity::createTemporaryDirectory('test', 0);
594
        $dir2 = $Dir2['dir'];
595
        $base2 = $Dir2['base'];
596
        if ($dir2) {
597
            $this->storeTestResult(\core\common\Entity::L_OK, "Test directory is writable.");
598
            \core\common\Entity::rrmdir($dir2);
599
        } else {
600
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Test directory $base2 does not exist or is not writable!");
601
        }
602
        $Dir3 = \core\common\Entity::createTemporaryDirectory('logo', 0);
603
        $dir3 = $Dir3['dir'];
604
        $base3 = $Dir3['base'];
605
        if ($dir3) {
606
            $this->storeTestResult(\core\common\Entity::L_OK, "Logos cache directory is writable.");
607
            \core\common\Entity::rrmdir($dir3);
608
        } else {
609
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Logos cache directory $base3 does not exist or is not writable!");
610
        }
611
    }
612
613
    /**
614
     * test if all required locales are enabled
615
     * 
616
     * @return void
617
     */
618
    private function testLocales() {
619
        $locales = shell_exec("locale -a");
620
        $allthere = "";
621
        foreach (CONFIG['LANGUAGES'] as $onelanguage) {
622
            if (preg_match("/" . $onelanguage['locale'] . "/", $locales) == 0) {
623
                $allthere .= $onelanguage['locale'] . " ";
624
            }
625
        }
626
        if ($allthere == "") {
627
            $this->storeTestResult(\core\common\Entity::L_OK, "All of your configured locales are available on your system.");
628
        } else {
629
            $this->storeTestResult(\core\common\Entity::L_WARN, "Some of your configured locales (<strong>$allthere</strong>) are not installed and will not be displayed correctly!");
630
        }
631
    }
632
633
    const DEFAULTS = [
634
        ["SETTING" => CONFIG['APPEARANCE']['from-mail'],
635
            "DEFVALUE" => "[email protected]",
636
            "COMPLAINTSTRING" => "APPEARANCE/from-mail ",
637
            "REQUIRED" => FALSE,],
638
        ["SETTING" => CONFIG['APPEARANCE']['support-contact']['url'],
639
            "DEFVALUE" => "[email protected]?body=Only%20English%20language%20please!",
640
            "COMPLAINTSTRING" => "APPEARANCE/support-contact/url ",
641
            "REQUIRED" => FALSE,],
642
        ["SETTING" => CONFIG['APPEARANCE']['support-contact']['display'],
643
            "DEFVALUE" => "[email protected]",
644
            "COMPLAINTSTRING" => "APPEARANCE/support-contact/display ",
645
            "REQUIRED" => FALSE,],
646
        ["SETTING" => CONFIG['APPEARANCE']['support-contact']['developer-mail'],
647
            "DEFVALUE" => "[email protected]",
648
            "COMPLAINTSTRING" => "APPEARANCE/support-contact/mail ",
649
            "REQUIRED" => FALSE,],
650
        ["SETTING" => CONFIG['APPEARANCE']['abuse-mail'],
651
            "DEFVALUE" => "[email protected]",
652
            "COMPLAINTSTRING" => "APPEARANCE/abuse-mail ",
653
            "REQUIRED" => FALSE,],
654
        ["SETTING" => CONFIG['APPEARANCE']['MOTD'],
655
            "DEFVALUE" => "Release Candidate. All bugs to be shot on sight!",
656
            "COMPLAINTSTRING" => "APPEARANCE/MOTD ",
657
            "REQUIRED" => FALSE,],
658
        ["SETTING" => CONFIG['APPEARANCE']['webcert_CRLDP'],
659
            "DEFVALUE" => ['list', 'of', 'CRL', 'pointers'],
660
            "COMPLAINTSTRING" => "APPEARANCE/webcert_CRLDP ",
661
            "REQUIRED" => TRUE,],
662
        ["SETTING" => CONFIG['APPEARANCE']['webcert_OCSP'],
663
            "DEFVALUE" => ['list', 'of', 'OCSP', 'pointers'],
664
            "COMPLAINTSTRING" => "APPEARANCE/webcert_OCSP ",
665
            "REQUIRED" => TRUE,],
666
        ["SETTING" => CONFIG['DB']['INST']['host'],
667
            "DEFVALUE" => "db.host.example",
668
            "COMPLAINTSTRING" => "DB/INST ",
669
            "REQUIRED" => TRUE,],
670
        ["SETTING" => CONFIG['DB']['INST']['host'],
671
            "DEFVALUE" => "db.host.example",
672
            "COMPLAINTSTRING" => "DB/USER ",
673
            "REQUIRED" => TRUE,],
674
        ["SETTING" => CONFIG['DB']['EXTERNAL']['host'],
675
            "DEFVALUE" => "customerdb.otherhost.example",
676
            "COMPLAINTSTRING" => "DB/EXTERNAL ",
677
            "REQUIRED" => FALSE,],
678
    ];
679
680
    /**
681
     * test if defaults in the config have been replaced with some real values
682
     * 
683
     * @return void
684
     */
685
    private function testDefaults() {
686
        $defaultvalues = "";
687
        $missingvalues = "";
688
        // all the checks for equality with a shipped default value
689
        foreach (SanityTests::DEFAULTS as $oneCheckItem) {
690
            if ($oneCheckItem['REQUIRED'] && !$oneCheckItem['SETTING']) {
691
                $missingvalues .= $oneCheckItem["COMPLAINTSTRING"];
692
            } elseif ($oneCheckItem['SETTING'] == $oneCheckItem["DEFVALUE"]) {
693
                $defaultvalues .= $oneCheckItem["COMPLAINTSTRING"];
694
            }
695
        }
696
        // additional checks for defaults, which are not simple equality checks
697
        if (isset(CONFIG_DIAGNOSTICS['RADIUSTESTS']['UDP-hosts'][0]) && CONFIG_DIAGNOSTICS['RADIUSTESTS']['UDP-hosts'][0]['ip'] == "192.0.2.1") {
698
            $defaultvalues .= "RADIUSTESTS/UDP-hosts ";
699
        }
700
701
        foreach (CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-clientcerts'] as $cadata) {
702
            foreach ($cadata['certificates'] as $cert_files) {
703
                if (file_get_contents(ROOT . "/config/cli-certs/" . $cert_files['public']) === FALSE) {
704
                    $defaultvalues .= "CERTIFICATE/" . $cert_files['public'] . " ";
705
                }
706
                if (file_get_contents(ROOT . "/config/cli-certs/" . $cert_files['private']) === FALSE) {
707
                    $defaultvalues .= "CERTIFICATE/" . $cert_files['private'] . " ";
708
                }
709
            }
710
        }
711
712
        if ($defaultvalues != "") {
713
            $this->storeTestResult(\core\common\Entity::L_WARN, "Your configuration in config/config.php contains unchanged default values or links to inexistent files: <strong>$defaultvalues</strong>!");
714
        } else {
715
            $this->storeTestResult(\core\common\Entity::L_OK, "Your configuration does not contain any unchanged defaults, which is a good sign.");
716
        }
717
    }
718
719
    /**
720
     * test access to databases
721
     * 
722
     * @return void
723
     */
724
    private function testDatabases() {
725
        $databaseName1 = 'INST';
726
        try {
727
            $db1 = DBConnection::handle($databaseName1);
728
            $res1 = $db1->exec('SELECT * FROM profile_option_dict');
729
            if ($res1->num_rows == $this->profile_option_ct) {
730
                $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName1 database appears to be OK.");
731
            } else {
732
                $this->storeTestResult(\core\common\Entity::L_ERROR, "The $databaseName1 database is reacheable but probably not updated to this version of CAT.");
733
            }
734
        } catch (Exception $e) {
735
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Connection to the  $databaseName1 database failed");
736
        }
737
738
        $databaseName2 = 'USER';
739
        try {
740
            $db2 = DBConnection::handle($databaseName2);
741
            if (CONFIG_CONFASSISTANT['CONSORTIUM']['name'] == "eduroam" && isset(CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo']) && CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo'] == "Operations Team") { // SW: APPROVED
742
                $res2 = $db2->exec('desc view_admin');
743
                if ($res2->num_rows == $this->view_admin_ct) {
744
                    $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName2 database appears to be OK.");
745
                } else {
746
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "The $databaseName2 is reacheable but there is something wrong with the schema");
747
                }
748
            } else {
749
                $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName2 database appears to be OK.");
750
            }
751
        } catch (Exception $e) {
752
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Connection to the  $databaseName2 database failed");
753
        }
754
755
        $databaseName3 = 'EXTERNAL';
756
        if (!empty(CONFIG['DB'][$databaseName3])) {
757
            try {
758
                $db3 = DBConnection::handle($databaseName3);
759
                if (CONFIG_CONFASSISTANT['CONSORTIUM']['name'] == "eduroam" && isset(CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo']) && CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo'] == "Operations Team") { // SW: APPROVED
760
                    $res3 = $db3->exec('desc view_admin');
761
                    if ($res3->num_rows == $this->view_admin_ct) {
762
                        $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName3 database appears to be OK.");
763
                    } else {
764
                        $this->storeTestResult(\core\common\Entity::L_ERROR, "The $databaseName3 is reacheable but there is something wrong with the schema");
765
                    }
766
                } else {
767
                    $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName3 database appears to be OK.");
768
                }
769
            } catch (Exception $e) {
770
771
                $this->storeTestResult(\core\common\Entity::L_ERROR, "Connection to the  $databaseName3 database failed");
772
            }
773
        }
774
    }
775
776
    /**
777
     * test devices.php for the no_cache option
778
     * 
779
     * @return void
780
     */
781
    private function testDeviceCache() {
782
        if ((!empty(\devices\Devices::$Options['no_cache'])) && \devices\Devices::$Options['no_cache']) {
783
            $global_no_cache = 1;
784
        } else {
785
            $global_no_cache = 0;
786
        }
787
788
        if ($global_no_cache == 1) {
789
            $this->storeTestResult(\core\common\Entity::L_WARN, "Devices no_cache global option is set, this is not a good idea in a production setting\n");
790
        }
791
        $Devs = \devices\Devices::listDevices();
792
        $no_cache_dev = '';
793
        $no_cache_dev_count = 0;
794
        if ($global_no_cache) {
795
            foreach ($Devs as $dev => $D) {
796
                if (empty($D['options']['no_cache']) || $D['options']['no_cache'] != 0) {
797
                    $no_cache_dev .= $dev . " ";
798
                    $no_cache_dev_count++;
799
                }
800
            }
801
        } else {
802
            foreach ($Devs as $dev => $D) {
803
                if (!empty($D['options']['no_cache']) && $D['options']['no_cache'] != 0) {
804
                    $no_cache_dev .= $dev . " ";
805
                    $no_cache_dev_count++;
806
                }
807
            }
808
        }
809
810
811
        if ($no_cache_dev_count > 1) {
812
            $this->storeTestResult(\core\common\Entity::L_WARN, "The following devices will not be cached: $no_cache_dev");
813
        }
814
        if ($no_cache_dev_count == 1) {
815
            $this->storeTestResult(\core\common\Entity::L_WARN, "The following device will not be cached: $no_cache_dev");
816
        }
817
    }
818
819
    /**
820
     * test if mailer works
821
     * 
822
     * @return void
823
     */
824
    private function testMailer() {
825
        if (empty(CONFIG['APPEARANCE']['abuse-mail']) || CONFIG['APPEARANCE']['abuse-mail'] == "[email protected]") {
826
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Your abuse-mail has not been set, cannot continue with mailer tests.");
827
            return;
828
        }
829
        $mail = new \PHPMailer\PHPMailer\PHPMailer();
830
        $mail->isSMTP();
831
        $mail->Port = 587;
832
        $mail->SMTPAuth = true;
833
        $mail->SMTPSecure = 'tls';
834
        $mail->Host = CONFIG['MAILSETTINGS']['host'];
835
        $mail->Username = CONFIG['MAILSETTINGS']['user'];
836
        $mail->Password = CONFIG['MAILSETTINGS']['pass'];
837
        $mail->SMTPOptions = CONFIG['MAILSETTINGS']['options'];
838
        $mail->WordWrap = 72;
839
        $mail->isHTML(FALSE);
840
        $mail->CharSet = 'UTF-8';
841
        $mail->From = CONFIG['APPEARANCE']['from-mail'];
842
        $mail->FromName = CONFIG['APPEARANCE']['productname'] . " Invitation System";
843
        $mail->addAddress(CONFIG['APPEARANCE']['abuse-mail']);
844
        $mail->Subject = "testing CAT configuration mail";
845
        $mail->Body = "Testing CAT mailing\n";
846
        $sent = $mail->send();
847
        if ($sent) {
848
            $this->storeTestResult(\core\common\Entity::L_OK, "mailer settings appear to be working, check " . CONFIG['APPEARANCE']['abuse-mail'] . " mailbox if the message was receiced.");
849
        } else {
850
            $this->storeTestResult(\core\common\Entity::L_ERROR, "mailer settings failed, check the Config::MAILSETTINGS");
851
        }
852
    }
853
}
854