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
|
|
|
private static $config; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Load the config, if the database connected, pull in database settings. |
39
|
|
|
* |
40
|
|
|
* return &array |
41
|
|
|
*/ |
42
|
|
|
public static function load() |
43
|
|
|
{ |
44
|
|
|
if (!is_null(self::$config)) { |
45
|
|
|
return self::$config; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
self::loadFiles(); |
49
|
|
|
|
50
|
|
|
// Make sure the database is connected |
51
|
|
|
if (Eloquent::isConnected()) { |
52
|
|
|
// pull in the database config settings |
53
|
|
|
self::mergeDb(); |
54
|
|
|
|
55
|
|
|
// load graph types from the database |
56
|
|
|
self::loadGraphsFromDb(); |
57
|
|
|
|
58
|
|
|
// process $config to tidy up |
59
|
|
|
self::processConfig(true); |
60
|
|
|
} else { |
61
|
|
|
// just process $config |
62
|
|
|
self::processConfig(false); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
// set to global for legacy/external things |
66
|
|
|
global $config; |
|
|
|
|
67
|
|
|
$config = self::$config; |
68
|
|
|
|
69
|
|
|
return self::$config; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Reload the config from files/db |
74
|
|
|
* @return mixed |
75
|
|
|
*/ |
76
|
|
|
public static function reload() |
77
|
|
|
{ |
78
|
|
|
self::$config = null; |
79
|
|
|
return self::load(); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Load the user config from config.php, defaults.inc.php and definitions.inc.php, etc. |
84
|
|
|
* Erases existing config. |
85
|
|
|
* |
86
|
|
|
* @return array |
87
|
|
|
*/ |
88
|
|
|
private static function &loadFiles() |
89
|
|
|
{ |
90
|
|
|
$config = []; // start fresh |
91
|
|
|
|
92
|
|
|
$install_dir = realpath(__DIR__ . '/../'); |
93
|
|
|
$config['install_dir'] = $install_dir; |
94
|
|
|
|
95
|
|
|
// load defaults |
96
|
|
|
require $install_dir . '/includes/defaults.inc.php'; |
97
|
|
|
require $install_dir . '/includes/definitions.inc.php'; |
98
|
|
|
|
99
|
|
|
// import standard settings |
100
|
|
|
$macros = json_decode(file_get_contents($install_dir . '/misc/macros.json'), true); |
101
|
|
|
$config['alert']['macros']['rule'] = $macros; |
102
|
|
|
|
103
|
|
|
// Load user config |
104
|
|
|
@include $install_dir . '/config.php'; |
105
|
|
|
|
106
|
|
|
// set it |
107
|
|
|
self::$config = $config; |
108
|
|
|
|
109
|
|
|
return self::$config; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Get a config value, if non existent null (or default if set) will be returned |
115
|
|
|
* |
116
|
|
|
* @param string $key period separated config variable name |
117
|
|
|
* @param mixed $default optional value to return if the setting is not set |
118
|
|
|
* @return mixed |
119
|
|
|
*/ |
120
|
|
|
public static function get($key, $default = null) |
121
|
|
|
{ |
122
|
|
|
if (isset(self::$config[$key])) { |
123
|
|
|
return self::$config[$key]; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
if (!str_contains($key, '.')) { |
127
|
|
|
return $default; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
$keys = explode('.', $key); |
131
|
|
|
|
132
|
|
|
$curr = &self::$config; |
133
|
|
|
foreach ($keys as $k) { |
134
|
|
|
// do not add keys that don't exist |
135
|
|
|
if (!isset($curr[$k])) { |
136
|
|
|
return $default; |
137
|
|
|
} |
138
|
|
|
$curr = &$curr[$k]; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
if (is_null($curr)) { |
142
|
|
|
return $default; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
return $curr; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Unset a config setting |
150
|
|
|
* or multiple |
151
|
|
|
* |
152
|
|
|
* @param string|array $key |
153
|
|
|
*/ |
154
|
|
|
public static function forget($key) |
155
|
|
|
{ |
156
|
|
|
Arr::forget(self::$config, $key); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Get a setting from a device, if that is not set, |
161
|
|
|
* fall back to the global config setting prefixed by $global_prefix |
162
|
|
|
* The key must be the same for the global setting and the device setting. |
163
|
|
|
* |
164
|
|
|
* @param array $device Device array |
165
|
|
|
* @param string $key Name of setting to fetch |
166
|
|
|
* @param string $global_prefix specify where the global setting lives in the global config |
167
|
|
|
* @param mixed $default will be returned if the setting is not set on the device or globally |
168
|
|
|
* @return mixed |
169
|
|
|
*/ |
170
|
|
|
public static function getDeviceSetting($device, $key, $global_prefix = null, $default = null) |
171
|
|
|
{ |
172
|
|
|
if (isset($device[$key])) { |
173
|
|
|
return $device[$key]; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
if (isset($global_prefix)) { |
177
|
|
|
$key = "$global_prefix.$key"; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
return self::get($key, $default); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Get a setting from the $config['os'] array using the os of the given device |
185
|
|
|
* If that is not set, fallback to the same global config key |
186
|
|
|
* |
187
|
|
|
* @param string $os The os name |
188
|
|
|
* @param string $key period separated config variable name |
189
|
|
|
* @param mixed $default optional value to return if the setting is not set |
190
|
|
|
* @return mixed |
191
|
|
|
*/ |
192
|
|
|
public static function getOsSetting($os, $key, $default = null) |
193
|
|
|
{ |
194
|
|
|
if ($os) { |
195
|
|
|
if (isset(self::$config['os'][$os][$key])) { |
196
|
|
|
return self::$config['os'][$os][$key]; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if (!str_contains($key, '.')) { |
200
|
|
|
return self::get($key, $default); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
$os_key = "os.$os.$key"; |
204
|
|
|
if (self::has($os_key)) { |
205
|
|
|
return self::get($os_key); |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
return self::get($key, $default); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Get the merged array from the global and os settings for the specified key. |
214
|
|
|
* Removes any duplicates. |
215
|
|
|
* When the arrays have keys, os settings take precedence over global settings |
216
|
|
|
* |
217
|
|
|
* @param string $os The os name |
218
|
|
|
* @param string $key period separated config variable name |
219
|
|
|
* @param array $default optional array to return if the setting is not set |
220
|
|
|
* @return array |
221
|
|
|
*/ |
222
|
|
|
public static function getCombined($os, $key, $default = array()) |
223
|
|
|
{ |
224
|
|
|
if (!self::has($key)) { |
225
|
|
|
return self::get("os.$os.$key", $default); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
if (!isset(self::$config['os'][$os][$key])) { |
229
|
|
|
if (!str_contains($key, '.')) { |
230
|
|
|
return self::get($key, $default); |
231
|
|
|
} |
232
|
|
|
if (!self::has("os.$os.$key")) { |
233
|
|
|
return self::get($key, $default); |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
return array_unique(array_merge( |
238
|
|
|
(array)self::get($key, $default), |
239
|
|
|
(array)self::getOsSetting($os, $key, $default) |
240
|
|
|
)); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Set a variable in the global config |
245
|
|
|
* |
246
|
|
|
* @param mixed $key period separated config variable name |
247
|
|
|
* @param mixed $value |
248
|
|
|
* @param bool $persist set the setting in the database so it persists across runs |
249
|
|
|
* @param string $default default (only set when initially created) |
250
|
|
|
* @param string $descr webui description (only set when initially created) |
251
|
|
|
* @param string $group webui group (only set when initially created) |
252
|
|
|
* @param string $sub_group webui subgroup (only set when initially created) |
253
|
|
|
*/ |
254
|
|
|
public static function set($key, $value, $persist = false, $default = null, $descr = null, $group = null, $sub_group = null) |
255
|
|
|
{ |
256
|
|
|
if ($persist) { |
257
|
|
|
try { |
258
|
|
|
\App\Models\Config::updateOrCreate(['config_name' => $key], collect([ |
259
|
|
|
'config_name' => $key, |
260
|
|
|
'config_default' => $default, |
261
|
|
|
'config_descr' => $descr, |
262
|
|
|
'config_group' => $group, |
263
|
|
|
'config_sub_group' => $sub_group, |
264
|
|
|
])->filter(function ($value, $field) { |
|
|
|
|
265
|
|
|
return !is_null($value); |
266
|
|
|
})->put('config_value', $value)->toArray()); |
267
|
|
|
} catch (QueryException $e) { |
268
|
|
|
if (class_exists(\Log::class)) { |
269
|
|
|
\Log::error($e); |
270
|
|
|
} |
271
|
|
|
global $debug; |
|
|
|
|
272
|
|
|
if ($debug) { |
273
|
|
|
echo $e; |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
$keys = explode('.', $key); |
279
|
|
|
|
280
|
|
|
$curr = &self::$config; |
281
|
|
|
foreach ($keys as $k) { |
282
|
|
|
$curr = &$curr[$k]; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
$curr = $value; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Check if a setting is set |
290
|
|
|
* |
291
|
|
|
* @param string $key period separated config variable name |
292
|
|
|
* @return bool |
293
|
|
|
*/ |
294
|
|
|
public static function has($key) |
295
|
|
|
{ |
296
|
|
|
if (isset(self::$config[$key])) { |
297
|
|
|
return true; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
if (!str_contains($key, '.')) { |
301
|
|
|
return false; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
$keys = explode('.', $key); |
305
|
|
|
$last = array_pop($keys); |
306
|
|
|
|
307
|
|
|
$curr = &self::$config; |
308
|
|
|
foreach ($keys as $k) { |
309
|
|
|
// do not add keys that don't exist |
310
|
|
|
if (!isset($curr[$k])) { |
311
|
|
|
return false; |
312
|
|
|
} |
313
|
|
|
$curr = &$curr[$k]; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
return is_array($curr) && isset($curr[$last]); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* Serialise the whole configuration to json for use in external processes. |
321
|
|
|
* |
322
|
|
|
* @return string |
323
|
|
|
*/ |
324
|
|
|
public static function json_encode() |
325
|
|
|
{ |
326
|
|
|
return json_encode(self::$config); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Get the full configuration array |
331
|
|
|
* @return array |
332
|
|
|
*/ |
333
|
|
|
public static function getAll() |
334
|
|
|
{ |
335
|
|
|
return self::$config; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* merge the database config with the global config |
340
|
|
|
* Global config overrides db |
341
|
|
|
*/ |
342
|
|
|
private static function mergeDb() |
343
|
|
|
{ |
344
|
|
|
$db_config = []; |
345
|
|
|
|
346
|
|
|
try { |
347
|
|
|
\App\Models\Config::get(['config_name', 'config_value']) |
348
|
|
|
->each(function ($item) use (&$db_config) { |
349
|
|
|
Arr::set($db_config, $item->config_name, $item->config_value); |
350
|
|
|
}); |
351
|
|
|
} catch (QueryException $e) { |
352
|
|
|
// possibly table config doesn't exist yet |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
self::$config = array_replace_recursive($db_config, self::$config); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
private static function loadGraphsFromDb() |
359
|
|
|
{ |
360
|
|
|
try { |
361
|
|
|
$graph_types = GraphType::all()->toArray(); |
362
|
|
|
} catch (QueryException $e) { |
363
|
|
|
// possibly table config doesn't exist yet |
364
|
|
|
$graph_types = []; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
// load graph types from the database |
368
|
|
|
foreach ($graph_types as $graph) { |
369
|
|
|
$g = []; |
370
|
|
|
foreach ($graph as $k => $v) { |
371
|
|
|
if (strpos($k, 'graph_') == 0) { |
372
|
|
|
// remove leading 'graph_' from column name |
373
|
|
|
$key = str_replace('graph_', '', $k); |
374
|
|
|
} else { |
375
|
|
|
$key = $k; |
376
|
|
|
} |
377
|
|
|
$g[$key] = $v; |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
self::$config['graph_types'][$g['type']][$g['subtype']] = $g; |
381
|
|
|
} |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Proces the config after it has been loaded. |
386
|
|
|
* Make sure certain variables have been set properly and |
387
|
|
|
* |
388
|
|
|
* @param bool $persist Save binary locations and other settings to the database. |
389
|
|
|
*/ |
390
|
|
|
private static function processConfig($persist = true) |
391
|
|
|
{ |
392
|
|
|
if (!self::get('email_from')) { |
393
|
|
|
self::set('email_from', '"' . self::get('project_name') . '" <' . self::get('email_user') . '@' . php_uname('n') . '>'); |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
// If we're on SSL, let's properly detect it |
397
|
|
|
if (isset($_SERVER['HTTPS'])) { |
398
|
|
|
self::set('base_url', preg_replace('/^http:/', 'https:', self::get('base_url'))); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
// Define some variables if they aren't set by user definition in config.php |
402
|
|
|
self::setDefault('html_dir', '%s/html', ['install_dir']); |
403
|
|
|
self::setDefault('rrd_dir', '%s/rrd', ['install_dir']); |
404
|
|
|
self::setDefault('mib_dir', '%s/mibs', ['install_dir']); |
405
|
|
|
self::setDefault('log_dir', '%s/logs', ['install_dir']); |
406
|
|
|
self::setDefault('log_file', '%s/%s.log', ['log_dir', 'project_id']); |
407
|
|
|
self::setDefault('plugin_dir', '%s/plugins', ['html_dir']); |
408
|
|
|
self::setDefault('temp_dir', sys_get_temp_dir() ?: '/tmp'); |
409
|
|
|
// self::setDefault('email_from', '"%s" <%s@' . php_uname('n') . '>', ['project_name', 'email_user']); // FIXME email_from set because alerting config |
|
|
|
|
410
|
|
|
|
411
|
|
|
// deprecated variables |
412
|
|
|
self::deprecatedVariable('rrdgraph_real_95th', 'rrdgraph_real_percentile'); |
413
|
|
|
self::deprecatedVariable('fping_options.millisec', 'fping_options.interval'); |
414
|
|
|
self::deprecatedVariable('discovery_modules.cisco-vrf', 'discovery_modules.vrf'); |
415
|
|
|
self::deprecatedVariable('oxidized.group', 'oxidized.maps.group'); |
416
|
|
|
|
417
|
|
|
// make sure we have full path to binaries in case PATH isn't set |
418
|
|
|
foreach (array('fping', 'fping6', 'snmpgetnext', 'rrdtool', 'traceroute', 'traceroute6') as $bin) { |
419
|
|
|
if (!is_executable(self::get($bin))) { |
420
|
|
|
self::set($bin, self::locateBinary($bin), $persist, $bin, "Path to $bin", 'external', 'paths'); |
421
|
|
|
} |
422
|
|
|
} |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* Set default values for defaults that depend on other settings, if they are not already loaded |
427
|
|
|
* |
428
|
|
|
* @param string $key |
429
|
|
|
* @param string $value value to set to key or vsprintf() format string for values below |
430
|
|
|
* @param array $format_values array of keys to send to vsprintf() |
431
|
|
|
*/ |
432
|
|
|
private static function setDefault($key, $value, $format_values = []) |
433
|
|
|
{ |
434
|
|
|
if (!self::has($key)) { |
435
|
|
|
if (is_string($value)) { |
|
|
|
|
436
|
|
|
$format_values = array_map('self::get', $format_values); |
437
|
|
|
self::set($key, vsprintf($value, $format_values)); |
438
|
|
|
} else { |
439
|
|
|
self::set($key, $value); |
440
|
|
|
} |
441
|
|
|
} |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
/** |
445
|
|
|
* Copy data from old variables to new ones. |
446
|
|
|
* |
447
|
|
|
* @param $old |
448
|
|
|
* @param $new |
449
|
|
|
*/ |
450
|
|
|
private static function deprecatedVariable($old, $new) |
451
|
|
|
{ |
452
|
|
|
if (self::has($old)) { |
453
|
|
|
global $debug; |
|
|
|
|
454
|
|
|
if ($debug) { |
455
|
|
|
echo "Copied deprecated config $old to $new\n"; |
456
|
|
|
} |
457
|
|
|
self::set($new, self::get($old)); |
458
|
|
|
} |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
/** |
462
|
|
|
* Get just the database connection settings from config.php |
463
|
|
|
* |
464
|
|
|
* @return array (keys: db_host, db_port, db_name, db_user, db_pass, db_socket) |
465
|
|
|
*/ |
466
|
|
|
public static function getDatabaseSettings() |
467
|
|
|
{ |
468
|
|
|
// Do not access global $config in this function! |
469
|
|
|
|
470
|
|
|
$keys = $config = [ |
471
|
|
|
'db_host' => '', |
472
|
|
|
'db_port' => '', |
473
|
|
|
'db_name' => '', |
474
|
|
|
'db_user' => '', |
475
|
|
|
'db_pass' => '', |
476
|
|
|
'db_socket' => '', |
477
|
|
|
]; |
478
|
|
|
|
479
|
|
|
if (is_file(__DIR__ . '/../config.php')) { |
480
|
|
|
include __DIR__ . '/../config.php'; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
// Check for testing database |
484
|
|
|
if (isset($config['test_db_name'])) { |
485
|
|
|
putenv('DB_TEST_DATABASE=' . $config['test_db_name']); |
486
|
|
|
} |
487
|
|
|
if (isset($config['test_db_user'])) { |
488
|
|
|
putenv('DB_TEST_USERNAME=' . $config['test_db_user']); |
489
|
|
|
} |
490
|
|
|
if (isset($config['test_db_pass'])) { |
491
|
|
|
putenv('DB_TEST_PASSWORD=' . $config['test_db_pass']); |
492
|
|
|
} |
493
|
|
|
|
494
|
|
|
return array_intersect_key($config, $keys); // return only the db settings |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
/** |
498
|
|
|
* Locate the actual path of a binary |
499
|
|
|
* |
500
|
|
|
* @param $binary |
501
|
|
|
* @return mixed |
502
|
|
|
*/ |
503
|
|
|
public static function locateBinary($binary) |
504
|
|
|
{ |
505
|
|
|
if (!str_contains($binary, '/')) { |
506
|
|
|
$output = `whereis -b $binary`; |
507
|
|
|
$list = trim(substr($output, strpos($output, ':') + 1)); |
508
|
|
|
$targets = explode(' ', $list); |
509
|
|
|
foreach ($targets as $target) { |
510
|
|
|
if (is_executable($target)) { |
511
|
|
|
return $target; |
512
|
|
|
} |
513
|
|
|
} |
514
|
|
|
} |
515
|
|
|
return $binary; |
516
|
|
|
} |
517
|
|
|
} |
518
|
|
|
|
Instead of relying on
global
state, we recommend one of these alternatives:1. Pass all data via parameters
2. Create a class that maintains your state