Passed
Push — master ( a2c69c...be0438 )
by Tony
10:22
created

LibreNMS/Config.php (4 issues)

1
<?php
2
/**
3
 * Config.php
4
 *
5
 * Config convenience class to access and set config variables.
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 *
20
 * @package    LibreNMS
21
 * @link       http://librenms.org
22
 * @copyright  2017 Tony Murray
23
 * @author     Tony Murray <[email protected]>
24
 */
25
26
namespace LibreNMS;
27
28
use App\Models\GraphType;
29
use Illuminate\Database\QueryException;
30
use Illuminate\Support\Arr;
31
use LibreNMS\DB\Eloquent;
32
33
class Config
34
{
35
    /**
36
     * Load the config, if the database connected, pull in database settings.
37
     *
38
     * return &array
39
     */
40
    public static function &load()
41
    {
42
        global $config;
43
44
        self::loadFiles();
45
46
        // Make sure the database is connected
47
        if (Eloquent::isConnected() || (function_exists('dbIsConnected') && dbIsConnected())) {
48
            // pull in the database config settings
49
            self::mergeDb();
50
51
            // load graph types from the database
52
            self::loadGraphsFromDb();
53
54
            // process $config to tidy up
55
            self::processConfig(true);
56
        } else {
57
            // just process $config
58
            self::processConfig(false);
59
        }
60
61
        return $config;
62
    }
63
64
    /**
65
     * Load the user config from config.php, defaults.inc.php and definitions.inc.php, etc.
66
     * Erases existing config.
67
     *
68
     * @return array
69
     */
70
    private static function &loadFiles()
71
    {
72
        global $config;
73
74
        $config = []; // start fresh
75
76
        $install_dir = realpath(__DIR__ . '/../');
77
        $config['install_dir'] = $install_dir;
78
79
        // load defaults
80
        require $install_dir . '/includes/defaults.inc.php';
81
        require $install_dir . '/includes/definitions.inc.php';
82
83
        // import standard settings
84
        $macros = json_decode(file_get_contents($install_dir . '/misc/macros.json'), true);
85
        self::set('alert.macros.rule', $macros);
86
87
        // Load user config
88
        @include $install_dir . '/config.php';
89
90
        return $config;
91
    }
92
93
94
    /**
95
     * Get a config value, if non existent null (or default if set) will be returned
96
     *
97
     * @param string $key period separated config variable name
98
     * @param mixed $default optional value to return if the setting is not set
99
     * @return mixed
100
     */
101
    public static function get($key, $default = null)
102
    {
103
        global $config;
104
105
        if (isset($config[$key])) {
106
            return $config[$key];
107
        }
108
109
        if (!str_contains($key, '.')) {
110
            return $default;
111
        }
112
113
        $keys = explode('.', $key);
114
115
        $curr = &$config;
116
        foreach ($keys as $k) {
117
            // do not add keys that don't exist
118
            if (!isset($curr[$k])) {
119
                return $default;
120
            }
121
            $curr = &$curr[$k];
122
        }
123
124
        if (is_null($curr)) {
125
            return $default;
126
        }
127
128
        return $curr;
129
    }
130
131
    /**
132
     * Unset a config setting
133
     * or multiple
134
     *
135
     * @param string|array $key
136
     */
137
    public static function forget($key)
138
    {
139
        global $config;
140
        Arr::forget($config, $key);
141
    }
142
143
    /**
144
     * Get a setting from a device, if that is not set,
145
     * fall back to the global config setting prefixed by $global_prefix
146
     * The key must be the same for the global setting and the device setting.
147
     *
148
     * @param array $device Device array
149
     * @param string $key Name of setting to fetch
150
     * @param string $global_prefix specify where the global setting lives in the global config
151
     * @param mixed $default will be returned if the setting is not set on the device or globally
152
     * @return mixed
153
     */
154
    public static function getDeviceSetting($device, $key, $global_prefix = null, $default = null)
155
    {
156
        if (isset($device[$key])) {
157
            return $device[$key];
158
        }
159
160
        if (isset($global_prefix)) {
161
            $key = "$global_prefix.$key";
162
        }
163
164
        return self::get($key, $default);
165
    }
166
167
    /**
168
     * Get a setting from the $config['os'] array using the os of the given device
169
     * If that is not set, fallback to the same global config key
170
     *
171
     * @param string $os The os name
172
     * @param string $key period separated config variable name
173
     * @param mixed $default optional value to return if the setting is not set
174
     * @return mixed
175
     */
176
    public static function getOsSetting($os, $key, $default = null)
177
    {
178
        global $config;
179
180
        if ($os) {
181
            if (isset($config['os'][$os][$key])) {
182
                return $config['os'][$os][$key];
183
            }
184
185
            if (!str_contains($key, '.')) {
186
                return self::get($key, $default);
187
            }
188
189
            $os_key = "os.$os.$key";
190
            if (self::has($os_key)) {
191
                return self::get($os_key);
192
            }
193
        }
194
195
        return self::get($key, $default);
196
    }
197
198
    /**
199
     * Get the merged array from the global and os settings for the specified key.
200
     * Removes any duplicates.
201
     * When the arrays have keys, os settings take precedence over global settings
202
     *
203
     * @param string $os The os name
204
     * @param string $key period separated config variable name
205
     * @param array $default optional array to return if the setting is not set
206
     * @return array
207
     */
208
    public static function getCombined($os, $key, $default = array())
209
    {
210
        global $config;
211
212
        if (!self::has($key)) {
213
            return self::get("os.$os.$key", $default);
214
        }
215
216
        if (!isset($config['os'][$os][$key])) {
217
            if (!str_contains($key, '.')) {
218
                return self::get($key, $default);
219
            }
220
            if (!self::has("os.$os.$key")) {
221
                return self::get($key, $default);
222
            }
223
        }
224
225
        return array_unique(array_merge(
226
            (array)self::get($key, $default),
227
            (array)self::getOsSetting($os, $key, $default)
228
        ));
229
    }
230
231
    /**
232
     * Set a variable in the global config
233
     *
234
     * @param mixed $key period separated config variable name
235
     * @param mixed $value
236
     * @param bool $persist set the setting in the database so it persists across runs
237
     * @param string $default default (only set when initially created)
238
     * @param string $descr webui description (only set when initially created)
239
     * @param string $group webui group (only set when initially created)
240
     * @param string $sub_group webui subgroup (only set when initially created)
241
     */
242
    public static function set($key, $value, $persist = false, $default = null, $descr = null, $group = null, $sub_group = null)
243
    {
244
        global $config;
245
246
        if ($persist) {
247
            if (Eloquent::isConnected()) {
248
                try {
249
                    $config_array = collect([
250
                        'config_name' => $key,
251
                        'config_value' => $value,
252
                        'config_default' => $default,
253
                        'config_descr' => $descr,
254
                        'config_group' => $group,
255
                        'config_sub_group' => $sub_group,
256
                    ])->filter(function ($value) {
257
                        return !is_null($value);
258
                    })->toArray();
259
260
                    \App\Models\Config::updateOrCreate(['config_name' => $key], $config_array);
261
                } catch (QueryException $e) {
262
                    // possibly table config doesn't exist yet
263
                    global $debug;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
264
                    if ($debug) {
265
                        echo $e;
266
                    }
267
                }
268
            } else {
269
                $res = dbUpdate(array('config_value' => $value), 'config', '`config_name`=?', array($key));
270
                if (!$res && !dbFetchCell('SELECT 1 FROM `config` WHERE `config_name`=?', array($key))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $res of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
271
                    $insert = array(
272
                        'config_name' => $key,
273
                        'config_value' => $value,
274
                        'config_default' => $default,
275
                        'config_descr' => $descr,
276
                        'config_group' => $group,
277
                        'config_sub_group' => $sub_group,
278
                    );
279
                    dbInsert($insert, 'config');
280
                }
281
            }
282
        }
283
284
        $keys = explode('.', $key);
285
286
        $curr = &$config;
287
        foreach ($keys as $k) {
288
            $curr = &$curr[$k];
289
        }
290
291
        $curr = $value;
292
    }
293
294
    /**
295
     * Check if a setting is set
296
     *
297
     * @param string $key period separated config variable name
298
     * @return bool
299
     */
300
    public static function has($key)
301
    {
302
        global $config;
303
304
        if (isset($config[$key])) {
305
            return true;
306
        }
307
308
        if (!str_contains($key, '.')) {
309
            return false;
310
        }
311
312
        $keys = explode('.', $key);
313
        $last = array_pop($keys);
314
315
        $curr = &$config;
316
        foreach ($keys as $k) {
317
            // do not add keys that don't exist
318
            if (!isset($curr[$k])) {
319
                return false;
320
            }
321
            $curr = &$curr[$k];
322
        }
323
324
        return is_array($curr) && isset($curr[$last]);
325
    }
326
327
    /**
328
     * Serialise the whole configuration to json for use in external processes.
329
     *
330
     * @return string
331
     */
332
    public static function json_encode()
333
    {
334
        global $config;
335
336
        return json_encode($config);
337
    }
338
339
    /**
340
     * Get the full configuration array
341
     * @return array
342
     */
343
    public static function getAll()
344
    {
345
        global $config;
346
        return $config;
347
    }
348
349
    /**
350
     * merge the database config with the global config
351
     * Global config overrides db
352
     */
353
    private static function mergeDb()
354
    {
355
        global $config;
356
357
        $db_config = [];
358
359
        if (Eloquent::isConnected()) {
360
            try {
361
                \App\Models\Config::get(['config_name', 'config_value'])
362
                    ->each(function ($item) use (&$db_config) {
363
                        array_set($db_config, $item->config_name, $item->config_value);
0 ignored issues
show
Deprecated Code introduced by
The function array_set() has been deprecated: Arr::set() should be used directly instead. Will be removed in Laravel 6.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

363
                        /** @scrutinizer ignore-deprecated */ array_set($db_config, $item->config_name, $item->config_value);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
364
                    });
365
            } catch (QueryException $e) {
366
                // possibly table config doesn't exist yet
367
            }
368
369
        } else {
370
            foreach (dbFetchRows('SELECT `config_name`,`config_value` FROM `config`') as $obj) {
371
                self::assignArrayByPath($db_config, $obj['config_name'], $obj['config_value']);
372
            }
373
        }
374
375
        $config = array_replace_recursive($db_config, $config);
376
    }
377
378
    /**
379
     * Assign a value into the passed array by a path
380
     * 'snmp.version' = 'v1' becomes $arr['snmp']['version'] = 'v1'
381
     *
382
     * @param array $arr the array to insert the value into, will be modified in place
383
     * @param string $path the path to insert the value at
384
     * @param mixed $value the value to insert, will be type cast
385
     * @param string $separator path separator
386
     */
387
    private static function assignArrayByPath(&$arr, $path, $value, $separator = '.')
388
    {
389
        // type cast value. Is this needed here?
390
        if (filter_var($value, FILTER_VALIDATE_INT)) {
391
            $value = (int)$value;
392
        } elseif (filter_var($value, FILTER_VALIDATE_FLOAT)) {
393
            $value = (float)$value;
394
        } elseif (filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null) {
395
            $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
396
        }
397
398
        $keys = explode($separator, $path);
399
400
        // walk the array creating keys if they don't exist
401
        foreach ($keys as $key) {
402
            $arr = &$arr[$key];
403
        }
404
        // assign the variable
405
        $arr = $value;
406
    }
407
408
    private static function loadGraphsFromDb()
409
    {
410
        global $config;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
411
412
        if (Eloquent::isConnected()) {
413
            try {
414
                $graph_types = GraphType::all()->toArray();
415
            } catch (QueryException $e) {
416
                // possibly table config doesn't exist yet
417
                $graph_types = [];
418
            }
419
        } else {
420
            $graph_types = dbFetchRows('SELECT * FROM graph_types');
421
        }
422
423
        // load graph types from the database
424
        foreach ($graph_types as $graph) {
425
            $g = [];
426
            foreach ($graph as $k => $v) {
427
                if (strpos($k, 'graph_') == 0) {
428
                    // remove leading 'graph_' from column name
429
                    $key = str_replace('graph_', '', $k);
430
                } else {
431
                    $key = $k;
432
                }
433
                $g[$key] = $v;
434
            }
435
436
            $config['graph_types'][$g['type']][$g['subtype']] = $g;
437
        }
438
    }
439
440
    /**
441
     * Proces the config after it has been loaded.
442
     * Make sure certain variables have been set properly and
443
     *
444
     * @param bool $persist Save binary locations and other settings to the database.
445
     */
446
    private static function processConfig($persist = true)
447
    {
448
        if (!self::get('email_from')) {
449
            self::set('email_from', '"' . self::get('project_name') . '" <' . self::get('email_user') . '@' . php_uname('n') . '>');
450
        }
451
452
        // If we're on SSL, let's properly detect it
453
        if (isset($_SERVER['HTTPS'])) {
454
            self::set('base_url', preg_replace('/^http:/', 'https:', self::get('base_url')));
455
        }
456
457
        // Define some variables if they aren't set by user definition in config.php
458
        self::setDefault('html_dir', '%s/html', ['install_dir']);
459
        self::setDefault('rrd_dir', '%s/rrd', ['install_dir']);
460
        self::setDefault('mib_dir', '%s/mibs', ['install_dir']);
461
        self::setDefault('log_dir', '%s/logs', ['install_dir']);
462
        self::setDefault('log_file', '%s/%s.log', ['log_dir', 'project_id']);
463
        self::setDefault('plugin_dir', '%s/plugins', ['html_dir']);
464
        self::setDefault('temp_dir', sys_get_temp_dir() ?: '/tmp');
465
//        self::setDefault('email_from', '"%s" <%s@' . php_uname('n') . '>', ['project_name', 'email_user']);  // FIXME email_from set because alerting config
466
467
        // deprecated variables
468
        self::deprecatedVariable('rrdgraph_real_95th', 'rrdgraph_real_percentile');
469
        self::deprecatedVariable('fping_options.millisec', 'fping_options.interval');
470
        self::deprecatedVariable('discovery_modules.cisco-vrf', 'discovery_modules.vrf');
471
        self::deprecatedVariable('oxidized.group', 'oxidized.maps.group');
472
473
        // make sure we have full path to binaries in case PATH isn't set
474
        foreach (array('fping', 'fping6', 'snmpgetnext', 'rrdtool', 'traceroute', 'traceroute6') as $bin) {
475
            if (!is_executable(self::get($bin))) {
476
                self::set($bin, self::locateBinary($bin), $persist, $bin, "Path to $bin", 'external', 'paths');
477
            }
478
        }
479
    }
480
481
    /**
482
     * Set default values for defaults that depend on other settings, if they are not already loaded
483
     *
484
     * @param string $key
485
     * @param string $value value to set to key or vsprintf() format string for values below
486
     * @param array $format_values array of keys to send to vsprintf()
487
     */
488
    private static function setDefault($key, $value, $format_values = [])
489
    {
490
        if (!self::has($key)) {
491
            if (is_string($value)) {
492
                $format_values = array_map('self::get', $format_values);
493
                self::set($key, vsprintf($value, $format_values));
494
            } else {
495
                self::set($key, $value);
496
            }
497
        }
498
    }
499
500
    /**
501
     * Copy data from old variables to new ones.
502
     *
503
     * @param $old
504
     * @param $new
505
     */
506
    private static function deprecatedVariable($old, $new)
507
    {
508
        if (self::has($old)) {
509
            global $debug;
510
            if ($debug) {
511
                echo "Copied deprecated config $old to $new\n";
512
            }
513
            self::set($new, self::get($old));
514
        }
515
    }
516
517
    /**
518
     * Get just the database connection settings from config.php
519
     *
520
     * @return array (keys: db_host, db_port, db_name, db_user, db_pass, db_socket)
521
     */
522
    public static function getDatabaseSettings()
523
    {
524
        // Do not access global $config in this function!
525
526
        $keys = $config = [
527
            'db_host' => '',
528
            'db_port' => '',
529
            'db_name' => '',
530
            'db_user' => '',
531
            'db_pass' => '',
532
            'db_socket' => '',
533
        ];
534
535
        if (is_file(__DIR__ . '/../config.php')) {
536
            include __DIR__ . '/../config.php';
537
        }
538
539
        // Check for testing database
540
        if (isset($config['test_db_name'])) {
541
            putenv('DB_TEST_DATABASE=' . $config['test_db_name']);
542
        }
543
        if (isset($config['test_db_user'])) {
544
            putenv('DB_TEST_USERNAME=' . $config['test_db_user']);
545
        }
546
        if (isset($config['test_db_pass'])) {
547
            putenv('DB_TEST_PASSWORD=' . $config['test_db_pass']);
548
        }
549
550
        return array_intersect_key($config, $keys); // return only the db settings
551
    }
552
553
    /**
554
     * Locate the actual path of a binary
555
     *
556
     * @param $binary
557
     * @return mixed
558
     */
559
    public static function locateBinary($binary)
560
    {
561
        if (!str_contains($binary, '/')) {
562
            $output = `whereis -b $binary`;
563
            $list = trim(substr($output, strpos($output, ':') + 1));
564
            $targets = explode(' ', $list);
565
            foreach ($targets as $target) {
566
                if (is_executable($target)) {
567
                    return $target;
568
                }
569
            }
570
        }
571
        return $binary;
572
    }
573
}
574