Test Failed
Push — master ( 341c02...c6d5e7 )
by Stefan
07:03
created

SanityTests::testRADIUSProbes()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 12
rs 9.9666
c 0
b 0
f 0
cc 4
nc 6
nop 0
1
<?php
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
    private function testRADIUSProbes() {
283
        $probeReturns = [];
284
        foreach (CONFIG_DIAGNOSTICS['RADIUSTESTS']['UDP-hosts'] as $oneProbe) {
285
            $statusServer = new diag\RFC5997Tests($oneProbe['ip'], 1812, $oneProbe['secret']);
286
            if ($statusServer->statusServerCheck() !== diag\AbstractTest::RETVAL_OK) {
287
                $probeReturns[] = $oneProbe['display_name'];
288
            }
289
        }
290
        if (count($probeReturns) == 0) {
291
            $this->storeTestResult(common\Entity::L_OK, "All configured RADIUS/UDP probes are reachable.");
292
        } else {
293
            $this->storeTestResult(common\Entity::L_ERROR, "The following RADIUS probes are NOT reachable: ".implode(', ',$probeReturns));
294
        }
295
    }
296
297
    /**
298
     * test for simpleSAMLphp
299
     * 
300
     * @return void
301
     */
302
    private function testSsp() {
303
        if (!is_file(CONFIG['AUTHENTICATION']['ssp-path-to-autoloader'])) {
304
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>simpleSAMLphp</strong> not found!");
305
        } else {
306
            include_once CONFIG['AUTHENTICATION']['ssp-path-to-autoloader'];
307
            $SSPconfig = \SimpleSAML_Configuration::getInstance();
308
            $sspVersion = explode('.', $SSPconfig->getVersion());
309
            if ((int) $sspVersion[0] >= $this->ssp_needversion['major'] && (int) $sspVersion[1] >= $this->ssp_needversion['minor']) {
310
                $this->storeTestResult(\core\common\Entity::L_OK, "<strong>simpleSAMLphp</strong> is sufficently recent. You are running " . implode('.', $sspVersion));
311
            } else {
312
                $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>simpleSAMLphp</strong> is too old. We need at least " . implode('.', $this->ssp_needversion));
313
            }
314
        }
315
    }
316
317
    /**
318
     * test for security setting
319
     * 
320
     * @return void
321
     */
322
    private function testSecurity() {
323
        if (in_array("I do not care about security!", CONFIG['SUPERADMINS'])) {
324
            $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'!");
325
        }
326
    }
327
328
    /**
329
     * test if zip is available
330
     * 
331
     * @return void
332
     */
333
    private function testZip() {
334
        if (exec("which zip") != "") {
335
            $this->storeTestResult(\core\common\Entity::L_OK, "<strong>zip</strong> binary found.");
336
        } else {
337
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>zip</strong> not found in your \$PATH!");
338
        }
339
    }
340
341
    /**
342
     * test if eapol_test is available and recent enough
343
     * 
344
     * @return void
345
     */
346
    private function testEapoltest() {
347
        exec(CONFIG_DIAGNOSTICS['PATHS']['eapol_test'], $out, $retval);
348
        if ($retval == 255) {
349
            $o = preg_grep('/-o<server cert/', $out);
350
            if (count($o) > 0) {
351
                $this->storeTestResult(\core\common\Entity::L_OK, "<strong>eapol_test</strong> script found.");
352
            } else {
353
                $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>eapol_test</strong> found, but is too old!");
354
            }
355
        } else {
356
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>eapol_test</strong> not found!");
357
        }
358
    }
359
360
    /**
361
     * test if logdir exists and is writable
362
     * 
363
     * @return void
364
     */
365
    private function testLogdir() {
366
        if (fopen(CONFIG['PATHS']['logdir'] . "/debug.log", "a") == FALSE) {
367
            $this->storeTestResult(\core\common\Entity::L_WARN, "Log files in <strong>" . CONFIG['PATHS']['logdir'] . "</strong> are not writable!");
368
        } else {
369
            $this->storeTestResult(\core\common\Entity::L_OK, "Log directory is writable.");
370
        }
371
    }
372
373
    /**
374
     * test for required PHP modules
375
     * 
376
     * @return void
377
     */
378
    private function testPhpModules() {
379
        if (function_exists('idn_to_ascii')) {
380
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP can handle internationalisation.");
381
        } else {
382
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP can <strong>NOT</strong> handle internationalisation (idn_to_ascii() from php7.0-intl).");
383
        }
384
385
        if (function_exists('gettext')) {
386
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>GNU Gettext</strong> is installed.");
387
        } else {
388
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GNU Gettext</strong> not found!");
389
        }
390
391
        if (function_exists('openssl_sign')) {
392
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>OpenSSL</strong> is installed.");
393
        } else {
394
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>OpenSSL</strong> not found!");
395
        }
396
397
        if (class_exists('\Imagick')) {
398
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>Imagick</strong> is installed.");
399
        } else {
400
            $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>.");
401
        }
402
403
        if (function_exists('ImageCreate')) {
404
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>GD</strong> is installed.");
405
        } else {
406
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>GD</strong> not found!</a>.");
407
        }
408
409
        if (function_exists('mysqli_connect')) {
410
            $this->storeTestResult(\core\common\Entity::L_OK, "PHP extension <strong>MySQL</strong> is installed.");
411
        } else {
412
            $this->storeTestResult(\core\common\Entity::L_ERROR, "PHP extension <strong>MySQL</strong> not found!");
413
        }
414
    }
415
416
    /**
417
     * test if GeoIP is installed correctly
418
     * 
419
     * @return void
420
     */
421
    private function testGeoip() {
422
        $host_4 = '145.0.2.50';
423
        $host_6 = '2001:610:188:444::50';
424
        switch (CONFIG['GEOIP']['version']) {
425
            case 0:
426
                $this->storeTestResult(\core\common\Entity::L_REMARK, "As set in the config, no geolocation service will be used");
427
                break;
428
            case 1:
429
                if (!function_exists('geoip_record_by_name')) {
430
                    $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>.");
431
                    return;
432
                }
433
                $record = geoip_record_by_name($host_4);
434
                if ($record === FALSE) {
435
                    $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.");
436
                    return;
437
                }
438
                if ($record['city'] != 'Utrecht') {
439
                    $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.");
440
                    return;
441
                }
442
                $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>.");
443
                break;
444
            case 2:
445
                if (!is_file(CONFIG['GEOIP']['geoip2-path-to-autoloader'])) {
446
                    $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>.");
447
                    return;
448
                }
449
                if (!is_file(CONFIG['GEOIP']['geoip2-path-to-db'])) {
450
                    $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.");
451
                    return;
452
                }
453
                include_once CONFIG['GEOIP']['geoip2-path-to-autoloader'];
454
                $reader = new Reader(CONFIG['GEOIP']['geoip2-path-to-db']);
455
                try {
456
                    $record = $reader->city($host_4);
457
                } catch (Exception $e) {
458
                    $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.");
459
                    return;
460
                }
461
                if ($record->city->name != 'Utrecht') {
462
                    $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.");
463
                    return;
464
                }
465
                try {
466
                    $record = $reader->city($host_6);
467
                } catch (Exception $e) {
468
                    $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.");
469
                    return;
470
                }
471
                if ($record->city->name != 'Utrecht') {
472
                    $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.");
473
                    return;
474
                }
475
                $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.");
476
                break;
477
            default:
478
                $this->storeTestResult(\core\common\Entity::L_ERROR, 'Check CONFIG[\'GEOIP\'][\'version\'], it must be set to either 1 or 2');
479
                break;
480
        }
481
    }
482
483
    /**
484
     * test if openssl is available
485
     * 
486
     * @return void
487
     */
488
    private function testOpenssl() {
489
        $A = $this->getExecPath('openssl');
490
        if ($A['exec'] != "") {
491
            $t = exec($A['exec'] . ' version');
492
            if ($A['exec_is'] == "EXPLICIT") {
493
                $this->storeTestResult(\core\common\Entity::L_OK, "<strong>$t</strong> was found and is configured explicitly in your config.");
494
            } else {
495
                $this->storeTestResult(\core\common\Entity::L_WARN, "<strong>$t</strong> was found, but is not configured with an absolute path in your config.");
496
            }
497
        } else {
498
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>openssl</strong> was not found on your system!");
499
        }
500
    }
501
502
    /**
503
     * test if makensis is available
504
     * 
505
     * @return void
506
     */
507
    private function testMakensis() {
508
        if (!is_numeric(CONFIG_CONFASSISTANT['NSIS_VERSION'])) {
509
            $this->storeTestResult(\core\common\Entity::L_ERROR, "NSIS_VERSION needs to be numeric!");
510
            return;
511
        }
512
        if (CONFIG_CONFASSISTANT['NSIS_VERSION'] < 2) {
513
            $this->storeTestResult(\core\common\Entity::L_ERROR, "NSIS_VERSION needs to be at least 2!");
514
            return;
515
        }
516
        $A = $this->getExecPath('makensis');
517
        if ($A['exec'] != "") {
518
            $t = exec($A['exec'] . ' -VERSION');
519
            if ($A['exec_is'] == "EXPLICIT") {
520
                $this->storeTestResult(\core\common\Entity::L_OK, "<strong>makensis $t</strong> was found and is configured explicitly in your config.");
521
            } else {
522
                $this->storeTestResult(\core\common\Entity::L_WARN, "<strong>makensis $t</strong> was found, but is not configured with an absolute path in your config.");
523
            }
524
            $outputArray = [];
525
            exec($A['exec'] . ' -HELP', $outputArray);
526
            $t1 = count(preg_grep('/INPUTCHARSET/', $outputArray));
527
            if ($t1 == 1 && CONFIG_CONFASSISTANT['NSIS_VERSION'] == 2) {
528
                $this->storeTestResult(\core\common\Entity::L_ERROR, "Declared NSIS_VERSION does not seem to match the file pointed to by PATHS['makensis']!");
529
            }
530
            if ($t1 == 0 && CONFIG_CONFASSISTANT['NSIS_VERSION'] >= 3) {
531
                $this->storeTestResult(\core\common\Entity::L_ERROR, "Declared NSIS_VERSION does not seem to match the file pointed to by PATHS['makensis']!");
532
            }
533
        } else {
534
            $this->storeTestResult(\core\common\Entity::L_ERROR, "<strong>makensis</strong> was not found on your system!");
535
        }
536
    }
537
538
    /**
539
     * test if all required NSIS modules are available
540
     * 
541
     * @return void
542
     */
543
    private function testNSISmodules() {
544
        $tmp_dir = \core\common\Entity::createTemporaryDirectory('installer', 0)['dir'];
545
        if (!chdir($tmp_dir)) {
546
            $this->loggerInstance->debug(2, "Cannot chdir to $tmp_dir\n");
547
            $this->storeTestResult(\core\common\Entity::L_ERROR, "NSIS modules test - problem with temporary directory permissions, cannot continue");
548
            return;
549
        }
550
        $exe = 'tt.exe';
551
        $NSIS_Module_status = [];
552
        foreach ($this->NSIS_Modules as $module) {
553
            unset($out);
554
            exec(CONFIG_CONFASSISTANT['PATHS']['makensis'] . " -V1 '-X!include $module' '-XOutFile $exe' '-XSection X' '-XSectionEnd'", $out, $retval);
555
            if ($retval > 0) {
556
                $NSIS_Module_status[$module] = 0;
557
            } else {
558
                $NSIS_Module_status[$module] = 1;
559
            }
560
        }
561
        if (is_file($exe)) {
562
            unlink($exe);
563
        }
564
        foreach ($NSIS_Module_status as $module => $status) {
565
            if ($status == 1) {
566
                $this->storeTestResult(\core\common\Entity::L_OK, "NSIS module <strong>$module</strong> was found.");
567
            } else {
568
                $this->storeTestResult(\core\common\Entity::L_ERROR, "NSIS module <strong>$module</strong> was not found or is not working correctly.");
569
            }
570
        }
571
    }
572
573
    /**
574
     * test access to dowloads directories
575
     * 
576
     * @return void
577
     */
578
    private function testDirectories() {
579
        $Dir1 = \core\common\Entity::createTemporaryDirectory('installer', 0);
580
        $dir1 = $Dir1['dir'];
581
        $base1 = $Dir1['base'];
582
        if ($dir1) {
583
            $this->storeTestResult(\core\common\Entity::L_OK, "Installer cache directory is writable.");
584
            \core\common\Entity::rrmdir($dir1);
585
        } else {
586
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Installer cache directory $base1 does not exist or is not writable!");
587
        }
588
        $Dir2 = \core\common\Entity::createTemporaryDirectory('test', 0);
589
        $dir2 = $Dir2['dir'];
590
        $base2 = $Dir2['base'];
591
        if ($dir2) {
592
            $this->storeTestResult(\core\common\Entity::L_OK, "Test directory is writable.");
593
            \core\common\Entity::rrmdir($dir2);
594
        } else {
595
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Test directory $base2 does not exist or is not writable!");
596
        }
597
        $Dir3 = \core\common\Entity::createTemporaryDirectory('logo', 0);
598
        $dir3 = $Dir3['dir'];
599
        $base3 = $Dir3['base'];
600
        if ($dir3) {
601
            $this->storeTestResult(\core\common\Entity::L_OK, "Logos cache directory is writable.");
602
            \core\common\Entity::rrmdir($dir3);
603
        } else {
604
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Logos cache directory $base3 does not exist or is not writable!");
605
        }
606
    }
607
608
    /**
609
     * test if all required locales are enabled
610
     * 
611
     * @return void
612
     */
613
    private function testLocales() {
614
        $locales = shell_exec("locale -a");
615
        $allthere = "";
616
        foreach (CONFIG['LANGUAGES'] as $onelanguage) {
617
            if (preg_match("/" . $onelanguage['locale'] . "/", $locales) == 0) {
618
                $allthere .= $onelanguage['locale'] . " ";
619
            }
620
        }
621
        if ($allthere == "") {
622
            $this->storeTestResult(\core\common\Entity::L_OK, "All of your configured locales are available on your system.");
623
        } else {
624
            $this->storeTestResult(\core\common\Entity::L_WARN, "Some of your configured locales (<strong>$allthere</strong>) are not installed and will not be displayed correctly!");
625
        }
626
    }
627
628
    const DEFAULTS = [
629
        ["SETTING" => CONFIG['APPEARANCE']['from-mail'],
630
            "DEFVALUE" => "[email protected]",
631
            "COMPLAINTSTRING" => "APPEARANCE/from-mail ",
632
            "REQUIRED" => FALSE,],
633
        ["SETTING" => CONFIG['APPEARANCE']['support-contact']['url'],
634
            "DEFVALUE" => "[email protected]?body=Only%20English%20language%20please!",
635
            "COMPLAINTSTRING" => "APPEARANCE/support-contact/url ",
636
            "REQUIRED" => FALSE,],
637
        ["SETTING" => CONFIG['APPEARANCE']['support-contact']['display'],
638
            "DEFVALUE" => "[email protected]",
639
            "COMPLAINTSTRING" => "APPEARANCE/support-contact/display ",
640
            "REQUIRED" => FALSE,],
641
        ["SETTING" => CONFIG['APPEARANCE']['support-contact']['developer-mail'],
642
            "DEFVALUE" => "[email protected]",
643
            "COMPLAINTSTRING" => "APPEARANCE/support-contact/mail ",
644
            "REQUIRED" => FALSE,],
645
        ["SETTING" => CONFIG['APPEARANCE']['abuse-mail'],
646
            "DEFVALUE" => "[email protected]",
647
            "COMPLAINTSTRING" => "APPEARANCE/abuse-mail ",
648
            "REQUIRED" => FALSE,],
649
        ["SETTING" => CONFIG['APPEARANCE']['MOTD'],
650
            "DEFVALUE" => "Release Candidate. All bugs to be shot on sight!",
651
            "COMPLAINTSTRING" => "APPEARANCE/MOTD ",
652
            "REQUIRED" => FALSE,],
653
        ["SETTING" => CONFIG['APPEARANCE']['webcert_CRLDP'],
654
            "DEFVALUE" => ['list', 'of', 'CRL', 'pointers'],
655
            "COMPLAINTSTRING" => "APPEARANCE/webcert_CRLDP ",
656
            "REQUIRED" => TRUE,],
657
        ["SETTING" => CONFIG['APPEARANCE']['webcert_OCSP'],
658
            "DEFVALUE" => ['list', 'of', 'OCSP', 'pointers'],
659
            "COMPLAINTSTRING" => "APPEARANCE/webcert_OCSP ",
660
            "REQUIRED" => TRUE,],
661
        ["SETTING" => CONFIG['DB']['INST']['host'],
662
            "DEFVALUE" => "db.host.example",
663
            "COMPLAINTSTRING" => "DB/INST ",
664
            "REQUIRED" => TRUE,],
665
        ["SETTING" => CONFIG['DB']['INST']['host'],
666
            "DEFVALUE" => "db.host.example",
667
            "COMPLAINTSTRING" => "DB/USER ",
668
            "REQUIRED" => TRUE,],
669
        ["SETTING" => CONFIG['DB']['EXTERNAL']['host'],
670
            "DEFVALUE" => "customerdb.otherhost.example",
671
            "COMPLAINTSTRING" => "DB/EXTERNAL ",
672
            "REQUIRED" => FALSE,],
673
    ];
674
675
    /**
676
     * test if defaults in the config have been replaced with some real values
677
     * 
678
     * @return void
679
     */
680
    private function testDefaults() {
681
        $defaultvalues = "";
682
        $missingvalues = "";
683
        // all the checks for equality with a shipped default value
684
        foreach (SanityTests::DEFAULTS as $oneCheckItem) {
685
            if ($oneCheckItem['REQUIRED'] && !$oneCheckItem['SETTING']) {
686
                $missingvalues .= $oneCheckItem["COMPLAINTSTRING"];
687
            } elseif ($oneCheckItem['SETTING'] == $oneCheckItem["DEFVALUE"]) {
688
                $defaultvalues .= $oneCheckItem["COMPLAINTSTRING"];
689
            }
690
        }
691
        // additional checks for defaults, which are not simple equality checks
692
        if (isset(CONFIG_DIAGNOSTICS['RADIUSTESTS']['UDP-hosts'][0]) && CONFIG_DIAGNOSTICS['RADIUSTESTS']['UDP-hosts'][0]['ip'] == "192.0.2.1") {
693
            $defaultvalues .= "RADIUSTESTS/UDP-hosts ";
694
        }
695
696
        foreach (CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-clientcerts'] as $cadata) {
697
            foreach ($cadata['certificates'] as $cert_files) {
698
                if (file_get_contents(ROOT . "/config/cli-certs/" . $cert_files['public']) === FALSE) {
699
                    $defaultvalues .= "CERTIFICATE/" . $cert_files['public'] . " ";
700
                }
701
                if (file_get_contents(ROOT . "/config/cli-certs/" . $cert_files['private']) === FALSE) {
702
                    $defaultvalues .= "CERTIFICATE/" . $cert_files['private'] . " ";
703
                }
704
            }
705
        }
706
707
        if ($defaultvalues != "") {
708
            $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>!");
709
        } else {
710
            $this->storeTestResult(\core\common\Entity::L_OK, "Your configuration does not contain any unchanged defaults, which is a good sign.");
711
        }
712
    }
713
714
    /**
715
     * test access to databases
716
     * 
717
     * @return void
718
     */
719
    private function testDatabases() {
720
        $databaseName1 = 'INST';
721
        try {
722
            $db1 = DBConnection::handle($databaseName1);
723
            $res1 = $db1->exec('SELECT * FROM profile_option_dict');
724
            if ($res1->num_rows == $this->profile_option_ct) {
725
                $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName1 database appears to be OK.");
726
            } else {
727
                $this->storeTestResult(\core\common\Entity::L_ERROR, "The $databaseName1 database is reacheable but probably not updated to this version of CAT.");
728
            }
729
        } catch (Exception $e) {
730
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Connection to the  $databaseName1 database failed");
731
        }
732
733
        $databaseName2 = 'USER';
734
        try {
735
            $db2 = DBConnection::handle($databaseName2);
736
            if (CONFIG_CONFASSISTANT['CONSORTIUM']['name'] == "eduroam" && isset(CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo']) && CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo'] == "Operations Team") { // SW: APPROVED
737
                $res2 = $db2->exec('desc view_admin');
738
                if ($res2->num_rows == $this->view_admin_ct) {
739
                    $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName2 database appears to be OK.");
740
                } else {
741
                    $this->storeTestResult(\core\common\Entity::L_ERROR, "The $databaseName2 is reacheable but there is something wrong with the schema");
742
                }
743
            } else {
744
                $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName2 database appears to be OK.");
745
            }
746
        } catch (Exception $e) {
747
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Connection to the  $databaseName2 database failed");
748
        }
749
750
        $databaseName3 = 'EXTERNAL';
751
        if (!empty(CONFIG['DB'][$databaseName3])) {
752
            try {
753
                $db3 = DBConnection::handle($databaseName3);
754
                if (CONFIG_CONFASSISTANT['CONSORTIUM']['name'] == "eduroam" && isset(CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo']) && CONFIG_CONFASSISTANT['CONSORTIUM']['deployment-voodoo'] == "Operations Team") { // SW: APPROVED
755
                    $res3 = $db3->exec('desc view_admin');
756
                    if ($res3->num_rows == $this->view_admin_ct) {
757
                        $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName3 database appears to be OK.");
758
                    } else {
759
                        $this->storeTestResult(\core\common\Entity::L_ERROR, "The $databaseName3 is reacheable but there is something wrong with the schema");
760
                    }
761
                } else {
762
                    $this->storeTestResult(\core\common\Entity::L_OK, "The $databaseName3 database appears to be OK.");
763
                }
764
            } catch (Exception $e) {
765
766
                $this->storeTestResult(\core\common\Entity::L_ERROR, "Connection to the  $databaseName3 database failed");
767
            }
768
        }
769
    }
770
771
    /**
772
     * test devices.php for the no_cache option
773
     * 
774
     * @return void
775
     */
776
    private function testDeviceCache() {
777
        if ((!empty(\devices\Devices::$Options['no_cache'])) && \devices\Devices::$Options['no_cache']) {
778
            $global_no_cache = 1;
779
        } else {
780
            $global_no_cache = 0;
781
        }
782
783
        if ($global_no_cache == 1) {
784
            $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");
785
        }
786
        $Devs = \devices\Devices::listDevices();
787
        $no_cache_dev = '';
788
        $no_cache_dev_count = 0;
789
        if ($global_no_cache) {
790
            foreach ($Devs as $dev => $D) {
791
                if (empty($D['options']['no_cache']) || $D['options']['no_cache'] != 0) {
792
                    $no_cache_dev .= $dev . " ";
793
                    $no_cache_dev_count++;
794
                }
795
            }
796
        } else {
797
            foreach ($Devs as $dev => $D) {
798
                if (!empty($D['options']['no_cache']) && $D['options']['no_cache'] != 0) {
799
                    $no_cache_dev .= $dev . " ";
800
                    $no_cache_dev_count++;
801
                }
802
            }
803
        }
804
805
806
        if ($no_cache_dev_count > 1) {
807
            $this->storeTestResult(\core\common\Entity::L_WARN, "The following devices will not be cached: $no_cache_dev");
808
        }
809
        if ($no_cache_dev_count == 1) {
810
            $this->storeTestResult(\core\common\Entity::L_WARN, "The following device will not be cached: $no_cache_dev");
811
        }
812
    }
813
814
    /**
815
     * test if mailer works
816
     * 
817
     * @return void
818
     */
819
    private function testMailer() {
820
        if (empty(CONFIG['APPEARANCE']['abuse-mail']) || CONFIG['APPEARANCE']['abuse-mail'] == "[email protected]") {
821
            $this->storeTestResult(\core\common\Entity::L_ERROR, "Your abuse-mail has not been set, cannot continue with mailer tests.");
822
            return;
823
        }
824
        $mail = new \PHPMailer\PHPMailer\PHPMailer();
825
        $mail->isSMTP();
826
        $mail->Port = 587;
827
        $mail->SMTPAuth = true;
828
        $mail->SMTPSecure = 'tls';
829
        $mail->Host = CONFIG['MAILSETTINGS']['host'];
830
        $mail->Username = CONFIG['MAILSETTINGS']['user'];
831
        $mail->Password = CONFIG['MAILSETTINGS']['pass'];
832
        $mail->SMTPOptions = CONFIG['MAILSETTINGS']['options'];
833
        $mail->WordWrap = 72;
834
        $mail->isHTML(FALSE);
835
        $mail->CharSet = 'UTF-8';
836
        $mail->From = CONFIG['APPEARANCE']['from-mail'];
837
        $mail->FromName = CONFIG['APPEARANCE']['productname'] . " Invitation System";
838
        $mail->addAddress(CONFIG['APPEARANCE']['abuse-mail']);
839
        $mail->Subject = "testing CAT configuration mail";
840
        $mail->Body = "Testing CAT mailing\n";
841
        $sent = $mail->send();
842
        if ($sent) {
843
            $this->storeTestResult(\core\common\Entity::L_OK, "mailer settings appear to be working, check " . CONFIG['APPEARANCE']['abuse-mail'] . " mailbox if the message was receiced.");
844
        } else {
845
            $this->storeTestResult(\core\common\Entity::L_ERROR, "mailer settings failed, check the Config::MAILSETTINGS");
846
        }
847
    }
848
849
    /**
850
     * TODO test if RADIUS connections work
851
     * 
852
     * @return void
853
     */
854
    private function testUDPhosts() {
855
//        if(empty)
856
    }
857
858
}
859