1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* LibreNMS |
5
|
|
|
* |
6
|
|
|
* This file is part of LibreNMS. |
7
|
|
|
* |
8
|
|
|
* @package LibreNMS |
9
|
|
|
* @subpackage functions |
10
|
|
|
* @copyright (C) 2006 - 2012 Adam Armstrong |
11
|
|
|
* |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
use Illuminate\Database\Events\QueryExecuted; |
15
|
|
|
use LibreNMS\Authentication\LegacyAuth; |
16
|
|
|
use LibreNMS\Config; |
|
|
|
|
17
|
|
|
use LibreNMS\Exceptions\HostExistsException; |
18
|
|
|
use LibreNMS\Exceptions\HostIpExistsException; |
19
|
|
|
use LibreNMS\Exceptions\HostUnreachableException; |
20
|
|
|
use LibreNMS\Exceptions\HostUnreachablePingException; |
21
|
|
|
use LibreNMS\Exceptions\InvalidPortAssocModeException; |
22
|
|
|
use LibreNMS\Exceptions\LockException; |
23
|
|
|
use LibreNMS\Exceptions\SnmpVersionUnsupportedException; |
24
|
|
|
use LibreNMS\Util\IPv4; |
25
|
|
|
use LibreNMS\Util\IPv6; |
26
|
|
|
use LibreNMS\Util\MemcacheLock; |
27
|
|
|
use Symfony\Component\Process\Process; |
28
|
|
|
use PHPMailer\PHPMailer\PHPMailer; |
29
|
|
|
use LibreNMS\Util\Time; |
30
|
|
|
|
31
|
|
|
if (!function_exists('set_debug')) { |
32
|
|
|
/** |
33
|
|
|
* Set debugging output |
34
|
|
|
* |
35
|
|
|
* @param bool $state If debug is enabled or not |
36
|
|
|
* @param bool $silence When not debugging, silence every php error |
37
|
|
|
* @return bool |
38
|
|
|
*/ |
39
|
|
|
function set_debug($state = true, $silence = false) |
40
|
|
|
{ |
41
|
|
|
global $debug; |
|
|
|
|
42
|
|
|
|
43
|
|
|
$debug = $state; // set to global |
44
|
|
|
|
45
|
|
|
restore_error_handler(); // disable Laravel error handler |
46
|
|
|
|
47
|
|
|
if (isset($debug) && $debug) { |
48
|
|
|
ini_set('display_errors', 1); |
49
|
|
|
ini_set('display_startup_errors', 1); |
50
|
|
|
ini_set('log_errors', 0); |
51
|
|
|
error_reporting(E_ALL & ~E_NOTICE); |
52
|
|
|
|
53
|
|
|
\LibreNMS\Util\Laravel::enableCliDebugOutput(); |
54
|
|
|
\LibreNMS\Util\Laravel::enableQueryDebug(); |
55
|
|
|
} else { |
56
|
|
|
ini_set('display_errors', 0); |
57
|
|
|
ini_set('display_startup_errors', 0); |
58
|
|
|
ini_set('log_errors', 1); |
59
|
|
|
error_reporting($silence ? 0 : E_ERROR); |
60
|
|
|
|
61
|
|
|
\LibreNMS\Util\Laravel::disableCliDebugOutput(); |
62
|
|
|
\LibreNMS\Util\Laravel::disableQueryDebug(); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
return $debug; |
66
|
|
|
} |
67
|
|
|
}//end set_debug() |
68
|
|
|
|
69
|
|
|
function array_sort_by_column($array, $on, $order = SORT_ASC) |
70
|
|
|
{ |
71
|
|
|
$new_array = array(); |
72
|
|
|
$sortable_array = array(); |
73
|
|
|
|
74
|
|
|
if (count($array) > 0) { |
75
|
|
|
foreach ($array as $k => $v) { |
76
|
|
|
if (is_array($v)) { |
77
|
|
|
foreach ($v as $k2 => $v2) { |
78
|
|
|
if ($k2 == $on) { |
79
|
|
|
$sortable_array[$k] = $v2; |
80
|
|
|
} |
81
|
|
|
} |
82
|
|
|
} else { |
83
|
|
|
$sortable_array[$k] = $v; |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
switch ($order) { |
88
|
|
|
case SORT_ASC: |
89
|
|
|
asort($sortable_array); |
90
|
|
|
break; |
91
|
|
|
case SORT_DESC: |
92
|
|
|
arsort($sortable_array); |
93
|
|
|
break; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
foreach ($sortable_array as $k => $v) { |
97
|
|
|
$new_array[$k] = $array[$k]; |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
return $new_array; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
function mac_clean_to_readable($mac) |
104
|
|
|
{ |
105
|
|
|
return \LibreNMS\Util\Rewrite::readableMac($mac); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
function only_alphanumeric($string) |
109
|
|
|
{ |
110
|
|
|
return preg_replace('/[^a-zA-Z0-9]/', '', $string); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Parse cli discovery or poller modules and set config for this run |
115
|
|
|
* |
116
|
|
|
* @param string $type discovery or poller |
117
|
|
|
* @param array $options get_opts array (only m key is checked) |
118
|
|
|
* @return bool |
119
|
|
|
*/ |
120
|
|
|
function parse_modules($type, $options) |
121
|
|
|
{ |
122
|
|
|
$override = false; |
123
|
|
|
|
124
|
|
|
if ($options['m']) { |
125
|
|
|
Config::set("{$type}_modules", []); |
126
|
|
|
foreach (explode(',', $options['m']) as $module) { |
127
|
|
|
// parse submodules (only supported by some modules) |
128
|
|
|
if (str_contains($module, '/')) { |
129
|
|
|
list($module, $submodule) = explode('/', $module, 2); |
130
|
|
|
$existing_submodules = Config::get("{$type}_submodules.$module", []); |
131
|
|
|
$existing_submodules[] = $submodule; |
132
|
|
|
Config::set("{$type}_submodules.$module", $existing_submodules); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$dir = $type == 'poller' ? 'polling' : $type; |
136
|
|
|
if (is_file("includes/$dir/$module.inc.php")) { |
137
|
|
|
Config::set("{$type}_modules.$module", 1); |
138
|
|
|
$override = true; |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
// display selected modules |
143
|
|
|
$modules = array_map(function ($module) use ($type) { |
144
|
|
|
$submodules = Config::get("{$type}_submodules.$module"); |
145
|
|
|
return $module . ($submodules ? '(' . implode(',', $submodules) . ')' : ''); |
146
|
|
|
}, array_keys(Config::get("{$type}_modules", []))); |
147
|
|
|
|
148
|
|
|
d_echo("Override $type modules: " . implode(', ', $modules) . PHP_EOL); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
return $override; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
function logfile($string) |
155
|
|
|
{ |
156
|
|
|
$fd = fopen(Config::get('log_file'), 'a'); |
157
|
|
|
fputs($fd, $string . "\n"); |
|
|
|
|
158
|
|
|
fclose($fd); |
|
|
|
|
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Detect the os of the given device. |
163
|
|
|
* |
164
|
|
|
* @param array $device device to check |
165
|
|
|
* @return string the name of the os |
166
|
|
|
*/ |
167
|
|
|
function getHostOS($device) |
168
|
|
|
{ |
169
|
|
|
$device['sysDescr'] = snmp_get($device, "SNMPv2-MIB::sysDescr.0", "-Ovq"); |
170
|
|
|
$device['sysObjectID'] = snmp_get($device, "SNMPv2-MIB::sysObjectID.0", "-Ovqn"); |
171
|
|
|
|
172
|
|
|
d_echo("| {$device['sysDescr']} | {$device['sysObjectID']} | \n"); |
173
|
|
|
|
174
|
|
|
$deferred_os = array( |
175
|
|
|
'freebsd', |
176
|
|
|
'linux', |
177
|
|
|
); |
178
|
|
|
|
179
|
|
|
// check yaml files |
180
|
|
|
$os_defs = Config::get('os'); |
181
|
|
|
foreach ($os_defs as $os => $def) { |
182
|
|
|
if (isset($def['discovery']) && !in_array($os, $deferred_os)) { |
183
|
|
|
foreach ($def['discovery'] as $item) { |
184
|
|
|
if (checkDiscovery($device, $item)) { |
185
|
|
|
return $os; |
186
|
|
|
} |
187
|
|
|
} |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
// check include files |
192
|
|
|
$os = null; |
193
|
|
|
$pattern = Config::get('install_dir') . '/includes/discovery/os/*.inc.php'; |
194
|
|
|
foreach (glob($pattern) as $file) { |
195
|
|
|
include $file; |
196
|
|
|
if (isset($os)) { |
197
|
|
|
return $os; |
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
// check deferred os |
202
|
|
|
foreach ($deferred_os as $os) { |
203
|
|
|
if (isset($os_defs[$os]['discovery'])) { |
204
|
|
|
foreach ($os_defs[$os]['discovery'] as $item) { |
205
|
|
|
if (checkDiscovery($device, $item)) { |
206
|
|
|
return $os; |
207
|
|
|
} |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
return 'generic'; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Check an array of conditions if all match, return true |
217
|
|
|
* sysObjectID if sysObjectID starts with any of the values under this item |
218
|
|
|
* sysDescr if sysDescr contains any of the values under this item |
219
|
|
|
* sysDescr_regex if sysDescr matches any of the regexes under this item |
220
|
|
|
* snmpget perform an snmpget on `oid` and check if the result contains `value`. Other subkeys: options, mib, mibdir |
221
|
|
|
* |
222
|
|
|
* Appending _except to any condition will invert the match. |
223
|
|
|
* |
224
|
|
|
* @param array $device |
225
|
|
|
* @param array $array Array of items, keys should be sysObjectID, sysDescr, or sysDescr_regex |
226
|
|
|
* @return bool the result (all items passed return true) |
227
|
|
|
*/ |
228
|
|
|
function checkDiscovery($device, $array) |
229
|
|
|
{ |
230
|
|
|
// all items must be true |
231
|
|
|
foreach ($array as $key => $value) { |
232
|
|
|
if ($check = ends_with($key, '_except')) { |
233
|
|
|
$key = substr($key, 0, -7); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
if ($key == 'sysObjectID') { |
237
|
|
|
if (starts_with($device['sysObjectID'], $value) == $check) { |
238
|
|
|
return false; |
239
|
|
|
} |
240
|
|
|
} elseif ($key == 'sysDescr') { |
241
|
|
|
if (str_contains($device['sysDescr'], $value) == $check) { |
242
|
|
|
return false; |
243
|
|
|
} |
244
|
|
|
} elseif ($key == 'sysDescr_regex') { |
245
|
|
|
if (preg_match_any($device['sysDescr'], $value) == $check) { |
246
|
|
|
return false; |
247
|
|
|
} |
248
|
|
|
} elseif ($key == 'sysObjectID_regex') { |
249
|
|
|
if (preg_match_any($device['sysObjectID'], $value) == $check) { |
250
|
|
|
return false; |
251
|
|
|
} |
252
|
|
|
} elseif ($key == 'snmpget') { |
253
|
|
|
$options = isset($value['options']) ? $value['options'] : '-Oqv'; |
254
|
|
|
$mib = isset($value['mib']) ? $value['mib'] : null; |
255
|
|
|
$mib_dir = isset($value['mib_dir']) ? $value['mib_dir'] : null; |
256
|
|
|
$op = isset($value['op']) ? $value['op'] : 'contains'; |
257
|
|
|
|
258
|
|
|
$get_value = snmp_get($device, $value['oid'], $options, $mib, $mib_dir); |
259
|
|
|
if (compare_var($get_value, $value['value'], $op) == $check) { |
260
|
|
|
return false; |
261
|
|
|
} |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
return true; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Check an array of regexes against a subject if any match, return true |
270
|
|
|
* |
271
|
|
|
* @param string $subject the string to match against |
272
|
|
|
* @param array|string $regexes an array of regexes or single regex to check |
273
|
|
|
* @return bool if any of the regexes matched, return true |
274
|
|
|
*/ |
275
|
|
|
function preg_match_any($subject, $regexes) |
276
|
|
|
{ |
277
|
|
|
foreach ((array)$regexes as $regex) { |
278
|
|
|
if (preg_match($regex, $subject)) { |
279
|
|
|
return true; |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
return false; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Perform comparison of two items based on give comparison method |
287
|
|
|
* Valid comparisons: =, !=, ==, !==, >=, <=, >, <, contains, starts, ends, regex |
288
|
|
|
* contains, starts, ends: $a haystack, $b needle(s) |
289
|
|
|
* regex: $a subject, $b regex |
290
|
|
|
* |
291
|
|
|
* @param mixed $a |
292
|
|
|
* @param mixed $b |
293
|
|
|
* @param string $comparison =, !=, ==, !== >=, <=, >, <, contains, starts, ends, regex |
294
|
|
|
* @return bool |
295
|
|
|
*/ |
296
|
|
|
function compare_var($a, $b, $comparison = '=') |
297
|
|
|
{ |
298
|
|
|
switch ($comparison) { |
299
|
|
|
case "=": |
300
|
|
|
return $a == $b; |
301
|
|
|
case "!=": |
302
|
|
|
return $a != $b; |
303
|
|
|
case "==": |
304
|
|
|
return $a === $b; |
305
|
|
|
case "!==": |
306
|
|
|
return $a !== $b; |
307
|
|
|
case ">=": |
308
|
|
|
return $a >= $b; |
309
|
|
|
case "<=": |
310
|
|
|
return $a <= $b; |
311
|
|
|
case ">": |
312
|
|
|
return $a > $b; |
313
|
|
|
case "<": |
314
|
|
|
return $a < $b; |
315
|
|
|
case "contains": |
316
|
|
|
return str_contains($a, $b); |
317
|
|
|
case "not_contains": |
318
|
|
|
return !str_contains($a, $b); |
319
|
|
|
case "starts": |
320
|
|
|
return starts_with($a, $b); |
321
|
|
|
case "not_starts": |
322
|
|
|
return !starts_with($a, $b); |
323
|
|
|
case "ends": |
324
|
|
|
return ends_with($a, $b); |
325
|
|
|
case "not_ends": |
326
|
|
|
return !ends_with($a, $b); |
327
|
|
|
case "regex": |
328
|
|
|
return (bool)preg_match($b, $a); |
329
|
|
|
case "not regex": |
330
|
|
|
return !((bool)preg_match($b, $a)); |
331
|
|
|
case "in_array": |
332
|
|
|
return in_array($a, $b); |
333
|
|
|
case "not_in_array": |
334
|
|
|
return !in_array($a, $b); |
335
|
|
|
default: |
336
|
|
|
return false; |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
function percent_colour($perc) |
341
|
|
|
{ |
342
|
|
|
$r = min(255, 5 * ($perc - 25)); |
343
|
|
|
$b = max(0, 255 - (5 * ($perc + 25))); |
344
|
|
|
|
345
|
|
|
return sprintf('#%02x%02x%02x', $r, $b, $b); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
// Returns the last in/out errors value in RRD |
349
|
|
|
function interface_errors($rrd_file, $period = '-1d') |
350
|
|
|
{ |
351
|
|
|
$errors = array(); |
352
|
|
|
|
353
|
|
|
$cmd = Config::get('rrdtool') . " fetch -s $period -e -300s $rrd_file AVERAGE | grep : | cut -d\" \" -f 4,5"; |
354
|
|
|
$data = trim(shell_exec($cmd)); |
355
|
|
|
$in_errors = 0; |
356
|
|
|
$out_errors = 0; |
357
|
|
|
foreach (explode("\n", $data) as $entry) { |
358
|
|
|
list($in, $out) = explode(" ", $entry); |
359
|
|
|
$in_errors += ($in * 300); |
360
|
|
|
$out_errors += ($out * 300); |
361
|
|
|
} |
362
|
|
|
$errors['in'] = round($in_errors); |
363
|
|
|
$errors['out'] = round($out_errors); |
364
|
|
|
|
365
|
|
|
return $errors; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* @param $device |
370
|
|
|
* @return string the logo image path for this device. Images are often wide, not square. |
371
|
|
|
*/ |
372
|
|
|
function getLogo($device) |
373
|
|
|
{ |
374
|
|
|
$img = getImageName($device, true, 'images/logos/'); |
375
|
|
|
if (!starts_with($img, 'generic')) { |
376
|
|
|
return 'images/logos/' . $img; |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
return getIcon($device); |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* @param array $device |
384
|
|
|
* @param string $class to apply to the image tag |
385
|
|
|
* @return string an image tag with the logo for this device. Images are often wide, not square. |
386
|
|
|
*/ |
387
|
|
|
function getLogoTag($device, $class = null) |
388
|
|
|
{ |
389
|
|
|
$tag = '<img src="' . url(getLogo($device)) . '" title="' . getImageTitle($device) . '"'; |
390
|
|
|
if (isset($class)) { |
391
|
|
|
$tag .= " class=\"$class\" "; |
392
|
|
|
} |
393
|
|
|
$tag .= ' />'; |
394
|
|
|
return $tag; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* @param $device |
399
|
|
|
* @return string the path to the icon image for this device. Close to square. |
400
|
|
|
*/ |
401
|
|
|
function getIcon($device) |
402
|
|
|
{ |
403
|
|
|
return 'images/os/' . getImageName($device); |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
/** |
407
|
|
|
* @param $device |
408
|
|
|
* @return string an image tag with the icon for this device. Close to square. |
409
|
|
|
*/ |
410
|
|
|
function getIconTag($device) |
411
|
|
|
{ |
412
|
|
|
return '<img src="' . getIcon($device) . '" title="' . getImageTitle($device) . '"/>'; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
function getImageTitle($device) |
416
|
|
|
{ |
417
|
|
|
return $device['icon'] ? str_replace(array('.svg', '.png'), '', $device['icon']) : $device['os']; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
function getImageName($device, $use_database = true, $dir = 'images/os/') |
421
|
|
|
{ |
422
|
|
|
return \LibreNMS\Util\Url::findOsImage($device['os'], $device['features'], $use_database ? $device['icon'] : null, $dir); |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
function renamehost($id, $new, $source = 'console') |
426
|
|
|
{ |
427
|
|
|
$host = gethostbyid($id); |
428
|
|
|
|
429
|
|
|
if (!is_dir(get_rrd_dir($new)) && rename(get_rrd_dir($host), get_rrd_dir($new)) === true) { |
430
|
|
|
dbUpdate(['hostname' => $new, 'ip' => null], 'devices', 'device_id=?', [$id]); |
431
|
|
|
log_event("Hostname changed -> $new ($source)", $id, 'system', 3); |
432
|
|
|
return ''; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
log_event("Renaming of $host failed", $id, 'system', 5); |
436
|
|
|
return "Renaming of $host failed\n"; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
function delete_device($id) |
440
|
|
|
{ |
441
|
|
|
global $debug; |
|
|
|
|
442
|
|
|
|
443
|
|
|
if (isCli() === false) { |
444
|
|
|
ignore_user_abort(true); |
445
|
|
|
set_time_limit(0); |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
$ret = ''; |
449
|
|
|
|
450
|
|
|
$host = dbFetchCell("SELECT hostname FROM devices WHERE device_id = ?", array($id)); |
451
|
|
|
if (empty($host)) { |
452
|
|
|
return "No such host."; |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
// Remove IPv4/IPv6 addresses before removing ports as they depend on port_id |
456
|
|
|
dbQuery("DELETE `ipv4_addresses` FROM `ipv4_addresses` INNER JOIN `ports` ON `ports`.`port_id`=`ipv4_addresses`.`port_id` WHERE `device_id`=?", array($id)); |
457
|
|
|
dbQuery("DELETE `ipv6_addresses` FROM `ipv6_addresses` INNER JOIN `ports` ON `ports`.`port_id`=`ipv6_addresses`.`port_id` WHERE `device_id`=?", array($id)); |
458
|
|
|
|
459
|
|
|
foreach (dbFetch("SELECT * FROM `ports` WHERE `device_id` = ?", array($id)) as $int_data) { |
460
|
|
|
$int_if = $int_data['ifDescr']; |
461
|
|
|
$int_id = $int_data['port_id']; |
462
|
|
|
delete_port($int_id); |
463
|
|
|
$ret .= "Removed interface $int_id ($int_if)\n"; |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
// Remove sensors manually due to constraints |
467
|
|
|
foreach (dbFetchRows("SELECT * FROM `sensors` WHERE `device_id` = ?", array($id)) as $sensor) { |
468
|
|
|
$sensor_id = $sensor['sensor_id']; |
469
|
|
|
dbDelete('sensors_to_state_indexes', "`sensor_id` = ?", array($sensor_id)); |
470
|
|
|
} |
471
|
|
|
$fields = array('device_id','host'); |
472
|
|
|
|
473
|
|
|
$db_name = dbFetchCell('SELECT DATABASE()'); |
474
|
|
|
foreach ($fields as $field) { |
475
|
|
|
foreach (dbFetch("SELECT table_name FROM information_schema.columns WHERE table_schema = ? AND column_name = ?", [$db_name, $field]) as $table) { |
476
|
|
|
$table = $table['table_name']; |
477
|
|
|
$entries = (int) dbDelete($table, "`$field` = ?", array($id)); |
478
|
|
|
if ($entries > 0 && $debug === true) { |
479
|
|
|
$ret .= "$field@$table = #$entries\n"; |
480
|
|
|
} |
481
|
|
|
} |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
$ex = shell_exec("bash -c '( [ ! -d ".trim(get_rrd_dir($host))." ] || rm -vrf ".trim(get_rrd_dir($host))." 2>&1 ) && echo -n OK'"); |
485
|
|
|
$tmp = explode("\n", $ex); |
486
|
|
|
if ($tmp[sizeof($tmp)-1] != "OK") { |
487
|
|
|
$ret .= "Could not remove files:\n$ex\n"; |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
$ret .= "Removed device $host\n"; |
491
|
|
|
log_event("Device $host has been removed", 0, 'system', 3); |
492
|
|
|
oxidized_reload_nodes(); |
493
|
|
|
return $ret; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Add a device to LibreNMS |
498
|
|
|
* |
499
|
|
|
* @param string $host dns name or ip address |
500
|
|
|
* @param string $snmp_version If this is empty, try v2c,v3,v1. Otherwise, use this specific version. |
501
|
|
|
* @param string $port the port to connect to for snmp |
502
|
|
|
* @param string $transport udp or tcp |
503
|
|
|
* @param string $poller_group the poller group this device will belong to |
504
|
|
|
* @param boolean $force_add add even if the device isn't reachable |
505
|
|
|
* @param string $port_assoc_mode snmp field to use to determine unique ports |
506
|
|
|
* @param array $additional an array with additional parameters to take into consideration when adding devices |
507
|
|
|
* |
508
|
|
|
* @return int returns the device_id of the added device |
509
|
|
|
* |
510
|
|
|
* @throws HostExistsException This hostname already exists |
511
|
|
|
* @throws HostIpExistsException We already have a host with this IP |
512
|
|
|
* @throws HostUnreachableException We could not reach this device is some way |
513
|
|
|
* @throws HostUnreachablePingException We could not ping the device |
514
|
|
|
* @throws InvalidPortAssocModeException The given port association mode was invalid |
515
|
|
|
* @throws SnmpVersionUnsupportedException The given snmp version was invalid |
516
|
|
|
*/ |
517
|
|
|
function addHost($host, $snmp_version = '', $port = '161', $transport = 'udp', $poller_group = '0', $force_add = false, $port_assoc_mode = 'ifIndex', $additional = array()) |
518
|
|
|
{ |
519
|
|
|
// Test Database Exists |
520
|
|
|
if (host_exists($host)) { |
521
|
|
|
throw new HostExistsException("Already have host $host"); |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
// Valid port assoc mode |
525
|
|
|
if (!in_array($port_assoc_mode, get_port_assoc_modes())) { |
526
|
|
|
throw new InvalidPortAssocModeException("Invalid port association_mode '$port_assoc_mode'. Valid modes are: " . join(', ', get_port_assoc_modes())); |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
// check if we have the host by IP |
530
|
|
|
if (Config::get('addhost_alwayscheckip') === true) { |
531
|
|
|
$ip = gethostbyname($host); |
532
|
|
|
} else { |
533
|
|
|
$ip = $host; |
534
|
|
|
} |
535
|
|
|
if ($force_add !== true && $device = device_has_ip($ip)) { |
536
|
|
|
$message = "Cannot add $host, already have device with this IP $ip"; |
537
|
|
|
if ($ip != $device->hostname) { |
538
|
|
|
$message .= " ($device->hostname)"; |
539
|
|
|
} |
540
|
|
|
$message .= '. You may force add to ignore this.'; |
541
|
|
|
throw new HostIpExistsException($message); |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
// Test reachability |
545
|
|
|
if (!$force_add) { |
546
|
|
|
$address_family = snmpTransportToAddressFamily($transport); |
547
|
|
|
$ping_result = isPingable($host, $address_family); |
548
|
|
|
if (!$ping_result['result']) { |
549
|
|
|
throw new HostUnreachablePingException("Could not ping $host"); |
550
|
|
|
} |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
// if $snmpver isn't set, try each version of snmp |
554
|
|
|
if (empty($snmp_version)) { |
555
|
|
|
$snmpvers = Config::get('snmp.version'); |
556
|
|
|
} else { |
557
|
|
|
$snmpvers = array($snmp_version); |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
if (isset($additional['snmp_disable']) && $additional['snmp_disable'] == 1) { |
561
|
|
|
return createHost($host, '', $snmp_version, $port, $transport, array(), $poller_group, 1, true, $additional); |
|
|
|
|
562
|
|
|
} |
563
|
|
|
$host_unreachable_exception = new HostUnreachableException("Could not connect to $host, please check the snmp details and snmp reachability"); |
564
|
|
|
// try different snmp variables to add the device |
565
|
|
|
foreach ($snmpvers as $snmpver) { |
566
|
|
|
if ($snmpver === "v3") { |
567
|
|
|
// Try each set of parameters from config |
568
|
|
|
foreach (Config::get('snmp.v3') as $v3) { |
569
|
|
|
$device = deviceArray($host, null, $snmpver, $port, $transport, $v3, $port_assoc_mode); |
570
|
|
|
if ($force_add === true || isSNMPable($device)) { |
571
|
|
|
return createHost($host, null, $snmpver, $port, $transport, $v3, $poller_group, $port_assoc_mode, $force_add); |
572
|
|
|
} else { |
573
|
|
|
$host_unreachable_exception->addReason("SNMP $snmpver: No reply with credentials " . $v3['authname'] . "/" . $v3['authlevel']); |
574
|
|
|
} |
575
|
|
|
} |
576
|
|
|
} elseif ($snmpver === "v2c" || $snmpver === "v1") { |
577
|
|
|
// try each community from config |
578
|
|
|
foreach (Config::get('snmp.community') as $community) { |
579
|
|
|
$device = deviceArray($host, $community, $snmpver, $port, $transport, null, $port_assoc_mode); |
580
|
|
|
|
581
|
|
|
if ($force_add === true || isSNMPable($device)) { |
582
|
|
|
return createHost($host, $community, $snmpver, $port, $transport, array(), $poller_group, $port_assoc_mode, $force_add); |
583
|
|
|
} else { |
584
|
|
|
$host_unreachable_exception->addReason("SNMP $snmpver: No reply with community $community"); |
585
|
|
|
} |
586
|
|
|
} |
587
|
|
|
} else { |
588
|
|
|
throw new SnmpVersionUnsupportedException("Unsupported SNMP Version \"$snmpver\", must be v1, v2c, or v3"); |
589
|
|
|
} |
590
|
|
|
} |
591
|
|
|
if (isset($additional['ping_fallback']) && $additional['ping_fallback'] == 1) { |
592
|
|
|
$additional['snmp_disable'] = 1; |
593
|
|
|
$additional['os'] = "ping"; |
594
|
|
|
return createHost($host, '', $snmp_version, $port, $transport, array(), $poller_group, 1, true, $additional); |
595
|
|
|
} |
596
|
|
|
throw $host_unreachable_exception; |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
function deviceArray($host, $community, $snmpver, $port = 161, $transport = 'udp', $v3 = array(), $port_assoc_mode = 'ifIndex') |
600
|
|
|
{ |
601
|
|
|
$device = array(); |
602
|
|
|
$device['hostname'] = $host; |
603
|
|
|
$device['port'] = $port; |
604
|
|
|
$device['transport'] = $transport; |
605
|
|
|
|
606
|
|
|
/* Get port_assoc_mode id if neccessary |
607
|
|
|
* We can work with names of IDs here */ |
608
|
|
|
if (! is_int($port_assoc_mode)) { |
609
|
|
|
$port_assoc_mode = get_port_assoc_mode_id($port_assoc_mode); |
610
|
|
|
} |
611
|
|
|
$device['port_association_mode'] = $port_assoc_mode; |
612
|
|
|
|
613
|
|
|
$device['snmpver'] = $snmpver; |
614
|
|
|
if ($snmpver === "v2c" or $snmpver === "v1") { |
|
|
|
|
615
|
|
|
$device['community'] = $community; |
616
|
|
|
} elseif ($snmpver === "v3") { |
617
|
|
|
$device['authlevel'] = $v3['authlevel']; |
618
|
|
|
$device['authname'] = $v3['authname']; |
619
|
|
|
$device['authpass'] = $v3['authpass']; |
620
|
|
|
$device['authalgo'] = $v3['authalgo']; |
621
|
|
|
$device['cryptopass'] = $v3['cryptopass']; |
622
|
|
|
$device['cryptoalgo'] = $v3['cryptoalgo']; |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
return $device; |
626
|
|
|
} |
627
|
|
|
|
628
|
|
|
|
629
|
|
|
function formatUptime($diff, $format = "long") |
630
|
|
|
{ |
631
|
|
|
return Time::formatInterval($diff, $format); |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
function isSNMPable($device) |
635
|
|
|
{ |
636
|
|
|
$pos = snmp_check($device); |
637
|
|
|
if ($pos === true) { |
638
|
|
|
return true; |
639
|
|
|
} else { |
640
|
|
|
$pos = snmp_get($device, "sysObjectID.0", "-Oqv", "SNMPv2-MIB"); |
641
|
|
|
if ($pos === '' || $pos === false) { |
642
|
|
|
return false; |
643
|
|
|
} else { |
644
|
|
|
return true; |
645
|
|
|
} |
646
|
|
|
} |
647
|
|
|
} |
648
|
|
|
|
649
|
|
|
/** |
650
|
|
|
* Check if the given host responds to ICMP echo requests ("pings"). |
651
|
|
|
* |
652
|
|
|
* @param string $hostname The hostname or IP address to send ping requests to. |
653
|
|
|
* @param string $address_family The address family ('ipv4' or 'ipv6') to use. Defaults to IPv4. |
654
|
|
|
* Will *not* be autodetected for IP addresses, so it has to be set to 'ipv6' when pinging an IPv6 address or an IPv6-only host. |
655
|
|
|
* @param array $attribs The device attributes |
656
|
|
|
* |
657
|
|
|
* @return array 'result' => bool pingable, 'last_ping_timetaken' => int time for last ping, 'db' => fping results |
658
|
|
|
*/ |
659
|
|
|
function isPingable($hostname, $address_family = 'ipv4', $attribs = []) |
660
|
|
|
{ |
661
|
|
|
if (can_ping_device($attribs) !== true) { |
662
|
|
|
return [ |
663
|
|
|
'result' => true, |
664
|
|
|
'last_ping_timetaken' => 0 |
665
|
|
|
]; |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
$status = fping( |
669
|
|
|
$hostname, |
670
|
|
|
Config::get('fping_options.count', 3), |
671
|
|
|
Config::get('fping_options.interval', 500), |
672
|
|
|
Config::get('fping_options.timeout', 500), |
673
|
|
|
$address_family |
674
|
|
|
); |
675
|
|
|
|
676
|
|
|
return [ |
677
|
|
|
'result' => ($status['exitcode'] == 0 && $status['loss'] < 100), |
|
|
|
|
678
|
|
|
'last_ping_timetaken' => $status['avg'], |
679
|
|
|
'db' => array_intersect_key($status, array_flip(['xmt','rcv','loss','min','max','avg'])) |
680
|
|
|
]; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
function getpollergroup($poller_group = '0') |
684
|
|
|
{ |
685
|
|
|
//Is poller group an integer |
686
|
|
|
if (is_int($poller_group) || ctype_digit($poller_group)) { |
687
|
|
|
return $poller_group; |
688
|
|
|
} else { |
689
|
|
|
//Check if it contains a comma |
690
|
|
|
if (strpos($poller_group, ',')!== false) { |
691
|
|
|
//If it has a comma use the first element as the poller group |
692
|
|
|
$poller_group_array=explode(',', $poller_group); |
693
|
|
|
return getpollergroup($poller_group_array[0]); |
694
|
|
|
} else { |
695
|
|
|
if (Config::get('distributed_poller_group')) { |
696
|
|
|
//If not use the poller's group from the config |
697
|
|
|
return getpollergroup(Config::get('distributed_poller_group')); |
698
|
|
|
} else { |
699
|
|
|
//If all else fails use default |
|
|
|
|
700
|
|
|
return '0'; |
701
|
|
|
} |
702
|
|
|
} |
703
|
|
|
} |
704
|
|
|
} |
705
|
|
|
|
706
|
|
|
/** |
707
|
|
|
* Add a host to the database |
708
|
|
|
* |
709
|
|
|
* @param string $host The IP or hostname to add |
710
|
|
|
* @param string $community The snmp community |
711
|
|
|
* @param string $snmpver snmp version: v1 | v2c | v3 |
712
|
|
|
* @param int $port SNMP port number |
713
|
|
|
* @param string $transport SNMP transport: udp | udp6 | udp | tcp6 |
714
|
|
|
* @param array $v3 SNMPv3 settings required array keys: authlevel, authname, authpass, authalgo, cryptopass, cryptoalgo |
715
|
|
|
* @param int $poller_group distributed poller group to assign this host to |
716
|
|
|
* @param string $port_assoc_mode field to use to identify ports: ifIndex, ifName, ifDescr, ifAlias |
717
|
|
|
* @param bool $force_add Do not detect the host os |
718
|
|
|
* @param array $additional an array with additional parameters to take into consideration when adding devices |
719
|
|
|
* @return int the id of the added host |
720
|
|
|
* @throws HostExistsException Throws this exception if the host already exists |
721
|
|
|
* @throws Exception Throws this exception if insertion into the database fails |
722
|
|
|
*/ |
723
|
|
|
function createHost( |
724
|
|
|
$host, |
725
|
|
|
$community, |
726
|
|
|
$snmpver, |
727
|
|
|
$port = 161, |
728
|
|
|
$transport = 'udp', |
729
|
|
|
$v3 = array(), |
730
|
|
|
$poller_group = 0, |
731
|
|
|
$port_assoc_mode = 'ifIndex', |
732
|
|
|
$force_add = false, |
733
|
|
|
$additional = array() |
734
|
|
|
) { |
735
|
|
|
$host = trim(strtolower($host)); |
736
|
|
|
|
737
|
|
|
$poller_group=getpollergroup($poller_group); |
738
|
|
|
|
739
|
|
|
/* Get port_assoc_mode id if necessary |
740
|
|
|
* We can work with names of IDs here */ |
741
|
|
|
if (! is_int($port_assoc_mode)) { |
|
|
|
|
742
|
|
|
$port_assoc_mode = get_port_assoc_mode_id($port_assoc_mode); |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
$device = array( |
746
|
|
|
'hostname' => $host, |
747
|
|
|
'sysName' => $additional['sysName'] ? $additional['sysName'] : $host, |
748
|
|
|
'os' => $additional['os'] ? $additional['os'] : 'generic', |
749
|
|
|
'hardware' => $additional['hardware'] ? $additional['hardware'] : null, |
750
|
|
|
'community' => $community, |
751
|
|
|
'port' => $port, |
752
|
|
|
'transport' => $transport, |
753
|
|
|
'status' => '1', |
754
|
|
|
'snmpver' => $snmpver, |
755
|
|
|
'poller_group' => $poller_group, |
756
|
|
|
'status_reason' => '', |
757
|
|
|
'port_association_mode' => $port_assoc_mode, |
758
|
|
|
'snmp_disable' => $additional['snmp_disable'] ? $additional['snmp_disable'] : 0, |
759
|
|
|
); |
760
|
|
|
|
761
|
|
|
$device = array_merge($device, $v3); // merge v3 settings |
762
|
|
|
|
763
|
|
|
if ($force_add !== true) { |
764
|
|
|
$device['os'] = getHostOS($device); |
765
|
|
|
|
766
|
|
|
$snmphost = snmp_get($device, "sysName.0", "-Oqv", "SNMPv2-MIB"); |
767
|
|
|
if (host_exists($host, $snmphost)) { |
|
|
|
|
768
|
|
|
throw new HostExistsException("Already have host $host ($snmphost) due to duplicate sysName"); |
769
|
|
|
} |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
$device_id = dbInsert($device, 'devices'); |
773
|
|
|
if ($device_id) { |
|
|
|
|
774
|
|
|
return $device_id; |
775
|
|
|
} |
776
|
|
|
|
777
|
|
|
throw new \Exception("Failed to add host to the database, please run ./validate.php"); |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
function isDomainResolves($domain) |
781
|
|
|
{ |
782
|
|
|
if (gethostbyname($domain) != $domain) { |
783
|
|
|
return true; |
784
|
|
|
} |
785
|
|
|
|
786
|
|
|
$records = dns_get_record($domain); // returns array or false |
|
|
|
|
787
|
|
|
return !empty($records); |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
function hoststatus($id) |
791
|
|
|
{ |
792
|
|
|
return dbFetchCell("SELECT `status` FROM `devices` WHERE `device_id` = ?", array($id)); |
793
|
|
|
} |
794
|
|
|
|
795
|
|
|
function match_network($nets, $ip, $first = false) |
796
|
|
|
{ |
797
|
|
|
$return = false; |
798
|
|
|
if (!is_array($nets)) { |
799
|
|
|
$nets = array ($nets); |
800
|
|
|
} |
801
|
|
|
foreach ($nets as $net) { |
802
|
|
|
$rev = (preg_match("/^\!/", $net)) ? true : false; |
803
|
|
|
$net = preg_replace("/^\!/", "", $net); |
804
|
|
|
$ip_arr = explode('/', $net); |
805
|
|
|
$net_long = ip2long($ip_arr[0]); |
806
|
|
|
$x = ip2long($ip_arr[1]); |
807
|
|
|
$mask = long2ip($x) == $ip_arr[1] ? $x : 0xffffffff << (32 - $ip_arr[1]); |
808
|
|
|
$ip_long = ip2long($ip); |
809
|
|
|
if ($rev) { |
810
|
|
|
if (($ip_long & $mask) == ($net_long & $mask)) { |
811
|
|
|
return false; |
812
|
|
|
} |
813
|
|
|
} else { |
814
|
|
|
if (($ip_long & $mask) == ($net_long & $mask)) { |
815
|
|
|
$return = true; |
816
|
|
|
} |
817
|
|
|
if ($first && $return) { |
818
|
|
|
return true; |
819
|
|
|
} |
820
|
|
|
} |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
return $return; |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
// FIXME port to LibreNMS\Util\IPv6 class |
827
|
|
|
function snmp2ipv6($ipv6_snmp) |
828
|
|
|
{ |
829
|
|
|
# Workaround stupid Microsoft bug in Windows 2008 -- this is fixed length! |
830
|
|
|
# < fenestro> "because whoever implemented this mib for Microsoft was ignorant of RFC 2578 section 7.7 (2)" |
831
|
|
|
$ipv6 = array_slice(explode('.', $ipv6_snmp), -16); |
832
|
|
|
$ipv6_2 = array(); |
833
|
|
|
|
834
|
|
|
for ($i = 0; $i <= 15; $i++) { |
835
|
|
|
$ipv6[$i] = zeropad(dechex($ipv6[$i])); |
836
|
|
|
} |
837
|
|
|
for ($i = 0; $i <= 15; $i+=2) { |
838
|
|
|
$ipv6_2[] = $ipv6[$i] . $ipv6[$i+1]; |
839
|
|
|
} |
840
|
|
|
|
841
|
|
|
return implode(':', $ipv6_2); |
842
|
|
|
} |
843
|
|
|
|
844
|
|
|
function get_astext($asn) |
845
|
|
|
{ |
846
|
|
|
global $cache; |
|
|
|
|
847
|
|
|
|
848
|
|
|
if (Config::has("astext.$asn")) { |
849
|
|
|
return Config::get("astext.$asn"); |
850
|
|
|
} |
851
|
|
|
|
852
|
|
|
if (isset($cache['astext'][$asn])) { |
853
|
|
|
return $cache['astext'][$asn]; |
854
|
|
|
} |
855
|
|
|
|
856
|
|
|
$result = @dns_get_record("AS$asn.asn.cymru.com", DNS_TXT); |
857
|
|
|
if (!empty($result[0]['txt'])) { |
858
|
|
|
$txt = explode('|', $result[0]['txt']); |
859
|
|
|
$result = trim($txt[4], ' "'); |
860
|
|
|
$cache['astext'][$asn] = $result; |
861
|
|
|
return $result; |
862
|
|
|
} |
863
|
|
|
|
864
|
|
|
return ''; |
865
|
|
|
} |
866
|
|
|
|
867
|
|
|
/** |
868
|
|
|
* Log events to the event table |
869
|
|
|
* |
870
|
|
|
* @param string $text message describing the event |
871
|
|
|
* @param array|int $device device array or device_id |
872
|
|
|
* @param string $type brief category for this event. Examples: sensor, state, stp, system, temperature, interface |
873
|
|
|
* @param int $severity 1: ok, 2: info, 3: notice, 4: warning, 5: critical, 0: unknown |
874
|
|
|
* @param int $reference the id of the referenced entity. Supported types: interface |
875
|
|
|
*/ |
876
|
|
|
function log_event($text, $device = null, $type = null, $severity = 2, $reference = null) |
877
|
|
|
{ |
878
|
|
|
if (!is_array($device)) { |
879
|
|
|
$device = device_by_id_cache($device); |
880
|
|
|
} |
881
|
|
|
|
882
|
|
|
dbInsert([ |
883
|
|
|
'device_id' => ($device['device_id'] ?: 0), |
884
|
|
|
'reference' => $reference, |
885
|
|
|
'type' => $type, |
886
|
|
|
'datetime' => \Carbon\Carbon::now(), |
887
|
|
|
'severity' => $severity, |
888
|
|
|
'message' => $text, |
889
|
|
|
'username' => isset(LegacyAuth::user()->username) ? LegacyAuth::user()->username : '', |
890
|
|
|
], 'eventlog'); |
891
|
|
|
} |
892
|
|
|
|
893
|
|
|
// Parse string with emails. Return array with email (as key) and name (as value) |
894
|
|
|
function parse_email($emails) |
895
|
|
|
{ |
896
|
|
|
$result = array(); |
897
|
|
|
$regex = '/^[\"\']?([^\"\']+)[\"\']?\s{0,}<([^@]+@[^>]+)>$/'; |
898
|
|
|
if (is_string($emails)) { |
899
|
|
|
$emails = preg_split('/[,;]\s{0,}/', $emails); |
900
|
|
|
foreach ($emails as $email) { |
901
|
|
|
if (preg_match($regex, $email, $out, PREG_OFFSET_CAPTURE)) { |
902
|
|
|
$result[$out[2][0]] = $out[1][0]; |
903
|
|
|
} else { |
904
|
|
|
if (strpos($email, "@")) { |
905
|
|
|
$from_name = Config::get('email_user'); |
906
|
|
|
$result[$email] = $from_name; |
907
|
|
|
} |
908
|
|
|
} |
909
|
|
|
} |
910
|
|
|
} else { |
911
|
|
|
// Return FALSE if input not string |
912
|
|
|
return false; |
913
|
|
|
} |
914
|
|
|
return $result; |
915
|
|
|
} |
916
|
|
|
|
917
|
|
|
function send_mail($emails, $subject, $message, $html = false) |
918
|
|
|
{ |
919
|
|
|
if (is_array($emails) || ($emails = parse_email($emails))) { |
920
|
|
|
d_echo("Attempting to email $subject to: " . implode('; ', array_keys($emails)) . PHP_EOL); |
921
|
|
|
$mail = new PHPMailer(true); |
922
|
|
|
try { |
923
|
|
|
$mail->Hostname = php_uname('n'); |
924
|
|
|
|
925
|
|
|
foreach (parse_email(Config::get('email_from')) as $from => $from_name) { |
926
|
|
|
$mail->setFrom($from, $from_name); |
927
|
|
|
} |
928
|
|
|
foreach ($emails as $email => $email_name) { |
929
|
|
|
$mail->addAddress($email, $email_name); |
930
|
|
|
} |
931
|
|
|
$mail->Subject = $subject; |
932
|
|
|
$mail->XMailer = Config::get('project_name_version'); |
933
|
|
|
$mail->CharSet = 'utf-8'; |
934
|
|
|
$mail->WordWrap = 76; |
935
|
|
|
$mail->Body = $message; |
936
|
|
|
if ($html) { |
937
|
|
|
$mail->isHTML(true); |
938
|
|
|
} |
939
|
|
|
switch (strtolower(trim(Config::get('email_backend')))) { |
940
|
|
|
case 'sendmail': |
941
|
|
|
$mail->Mailer = 'sendmail'; |
942
|
|
|
$mail->Sendmail = Config::get('email_sendmail_path'); |
943
|
|
|
break; |
944
|
|
|
case 'smtp': |
945
|
|
|
$mail->isSMTP(); |
946
|
|
|
$mail->Host = Config::get('email_smtp_host'); |
947
|
|
|
$mail->Timeout = Config::get('email_smtp_timeout'); |
948
|
|
|
$mail->SMTPAuth = Config::get('email_smtp_auth'); |
949
|
|
|
$mail->SMTPSecure = Config::get('email_smtp_secure'); |
950
|
|
|
$mail->Port = Config::get('email_smtp_port'); |
951
|
|
|
$mail->Username = Config::get('email_smtp_username'); |
952
|
|
|
$mail->Password = Config::get('email_smtp_password'); |
953
|
|
|
$mail->SMTPAutoTLS = Config::get('email_auto_tls'); |
954
|
|
|
$mail->SMTPDebug = false; |
|
|
|
|
955
|
|
|
break; |
956
|
|
|
default: |
957
|
|
|
$mail->Mailer = 'mail'; |
958
|
|
|
break; |
959
|
|
|
} |
960
|
|
|
$mail->send(); |
961
|
|
|
return true; |
962
|
|
|
} catch (\PHPMailer\PHPMailer\Exception $e) { |
963
|
|
|
return $e->errorMessage(); |
964
|
|
|
} catch (Exception $e) { |
965
|
|
|
return $e->getMessage(); |
966
|
|
|
} |
967
|
|
|
} |
968
|
|
|
|
969
|
|
|
return "No contacts found"; |
970
|
|
|
} |
971
|
|
|
|
972
|
|
|
function formatCiscoHardware(&$device, $short = false) |
973
|
|
|
{ |
974
|
|
|
return \LibreNMS\Util\Rewrite::ciscoHardware($device, $short); |
975
|
|
|
} |
976
|
|
|
|
977
|
|
|
function hex2str($hex) |
978
|
|
|
{ |
979
|
|
|
$string=''; |
980
|
|
|
|
981
|
|
|
for ($i = 0; $i < strlen($hex)-1; $i+=2) { |
982
|
|
|
$string .= chr(hexdec(substr($hex, $i, 2))); |
|
|
|
|
983
|
|
|
} |
984
|
|
|
|
985
|
|
|
return $string; |
986
|
|
|
} |
987
|
|
|
|
988
|
|
|
# Convert an SNMP hex string to regular string |
989
|
|
|
function snmp_hexstring($hex) |
990
|
|
|
{ |
991
|
|
|
return hex2str(str_replace(' ', '', str_replace(' 00', '', $hex))); |
992
|
|
|
} |
993
|
|
|
|
994
|
|
|
# Check if the supplied string is an SNMP hex string |
995
|
|
|
function isHexString($str) |
996
|
|
|
{ |
997
|
|
|
return (bool)preg_match("/^[a-f0-9][a-f0-9]( [a-f0-9][a-f0-9])*$/is", trim($str)); |
998
|
|
|
} |
999
|
|
|
|
1000
|
|
|
# Include all .inc.php files in $dir |
1001
|
|
|
function include_dir($dir, $regex = "") |
1002
|
|
|
{ |
1003
|
|
|
global $device, $valid; |
|
|
|
|
1004
|
|
|
|
1005
|
|
|
if ($regex == "") { |
1006
|
|
|
$regex = "/\.inc\.php$/"; |
1007
|
|
|
} |
1008
|
|
|
|
1009
|
|
|
if ($handle = opendir(Config::get('install_dir') . '/' . $dir)) { |
1010
|
|
|
while (false !== ($file = readdir($handle))) { |
1011
|
|
|
if (filetype(Config::get('install_dir') . '/' . $dir . '/' . $file) == 'file' && preg_match($regex, $file)) { |
1012
|
|
|
d_echo("Including: " . Config::get('install_dir') . '/' . $dir . '/' . $file . "\n"); |
1013
|
|
|
|
1014
|
|
|
include(Config::get('install_dir') . '/' . $dir . '/' . $file); |
1015
|
|
|
} |
1016
|
|
|
} |
1017
|
|
|
|
1018
|
|
|
closedir($handle); |
1019
|
|
|
} |
1020
|
|
|
} |
1021
|
|
|
|
1022
|
|
|
/** |
1023
|
|
|
* Check if port is valid to poll. |
1024
|
|
|
* Settings: empty_ifdescr, good_if, bad_if, bad_if_regexp, bad_ifname_regexp, bad_ifalias_regexp, bad_iftype |
1025
|
|
|
* |
1026
|
|
|
* @param array $port |
1027
|
|
|
* @param array $device |
1028
|
|
|
* @return bool |
1029
|
|
|
*/ |
1030
|
|
|
function is_port_valid($port, $device) |
1031
|
|
|
{ |
1032
|
|
|
// check empty values first |
1033
|
|
|
if (empty($port['ifDescr'])) { |
1034
|
|
|
// If these are all empty, we are just going to show blank names in the ui |
1035
|
|
|
if (empty($port['ifAlias']) && empty($port['ifName'])) { |
1036
|
|
|
d_echo("ignored: empty ifDescr, ifAlias and ifName\n"); |
1037
|
|
|
return false; |
1038
|
|
|
} |
1039
|
|
|
|
1040
|
|
|
// ifDescr should not be empty unless it is explicitly allowed |
1041
|
|
|
if (!Config::getOsSetting($device['os'], 'empty_ifdescr', false)) { |
1042
|
|
|
d_echo("ignored: empty ifDescr\n"); |
1043
|
|
|
return false; |
1044
|
|
|
} |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
|
|
$ifDescr = $port['ifDescr']; |
1048
|
|
|
$ifName = $port['ifName']; |
1049
|
|
|
$ifAlias = $port['ifAlias']; |
1050
|
|
|
$ifType = $port['ifType']; |
1051
|
|
|
|
1052
|
|
|
if (str_i_contains($ifDescr, Config::getOsSetting($device['os'], 'good_if'))) { |
1053
|
|
|
return true; |
1054
|
|
|
} |
1055
|
|
|
|
1056
|
|
|
foreach (Config::getCombined($device['os'], 'bad_if') as $bi) { |
1057
|
|
|
if (str_i_contains($ifDescr, $bi)) { |
1058
|
|
|
d_echo("ignored by ifDescr: $ifDescr (matched: $bi)\n"); |
1059
|
|
|
return false; |
1060
|
|
|
} |
1061
|
|
|
} |
1062
|
|
|
|
1063
|
|
|
foreach (Config::getCombined($device['os'], 'bad_if_regexp') as $bir) { |
1064
|
|
|
if (preg_match($bir ."i", $ifDescr)) { |
1065
|
|
|
d_echo("ignored by ifDescr: $ifDescr (matched: $bir)\n"); |
1066
|
|
|
return false; |
1067
|
|
|
} |
1068
|
|
|
} |
1069
|
|
|
|
1070
|
|
|
foreach (Config::getCombined($device['os'], 'bad_ifname_regexp') as $bnr) { |
1071
|
|
|
if (preg_match($bnr ."i", $ifName)) { |
1072
|
|
|
d_echo("ignored by ifName: $ifName (matched: $bnr)\n"); |
1073
|
|
|
return false; |
1074
|
|
|
} |
1075
|
|
|
} |
1076
|
|
|
|
1077
|
|
|
|
1078
|
|
|
foreach (Config::getCombined($device['os'], 'bad_ifalias_regexp') as $bar) { |
1079
|
|
|
if (preg_match($bar ."i", $ifAlias)) { |
1080
|
|
|
d_echo("ignored by ifName: $ifAlias (matched: $bar)\n"); |
1081
|
|
|
return false; |
1082
|
|
|
} |
1083
|
|
|
} |
1084
|
|
|
|
1085
|
|
|
foreach (Config::getCombined($device['os'], 'bad_iftype') as $bt) { |
1086
|
|
|
if (str_contains($ifType, $bt)) { |
1087
|
|
|
d_echo("ignored by ifType: $ifType (matched: $bt )\n"); |
1088
|
|
|
return false; |
1089
|
|
|
} |
1090
|
|
|
} |
1091
|
|
|
|
1092
|
|
|
return true; |
1093
|
|
|
} |
1094
|
|
|
|
1095
|
|
|
/** |
1096
|
|
|
* Try to fill in data for ifDescr, ifName, and ifAlias if devices do not provide them. |
1097
|
|
|
* Will not fill ifAlias if the user has overridden it |
1098
|
|
|
* |
1099
|
|
|
* @param array $port |
1100
|
|
|
* @param array $device |
1101
|
|
|
*/ |
1102
|
|
|
function port_fill_missing(&$port, $device) |
1103
|
|
|
{ |
1104
|
|
|
// When devices do not provide data, populate with other data if available |
1105
|
|
|
if ($port['ifDescr'] == '' || $port['ifDescr'] == null) { |
1106
|
|
|
$port['ifDescr'] = $port['ifName']; |
1107
|
|
|
d_echo(' Using ifName as ifDescr'); |
1108
|
|
|
} |
1109
|
|
|
if (!empty($device['attribs']['ifName:' . $port['ifName']])) { |
1110
|
|
|
// ifAlias overridden by user, don't update it |
1111
|
|
|
unset($port['ifAlias']); |
1112
|
|
|
d_echo(' ifAlias overriden by user'); |
1113
|
|
|
} elseif ($port['ifAlias'] == '' || $port['ifAlias'] == null) { |
1114
|
|
|
$port['ifAlias'] = $port['ifDescr']; |
1115
|
|
|
d_echo(' Using ifDescr as ifAlias'); |
1116
|
|
|
} |
1117
|
|
|
|
1118
|
|
|
if ($port['ifName'] == '' || $port['ifName'] == null) { |
1119
|
|
|
$port['ifName'] = $port['ifDescr']; |
1120
|
|
|
d_echo(' Using ifDescr as ifName'); |
1121
|
|
|
} |
1122
|
|
|
} |
1123
|
|
|
|
1124
|
|
|
function scan_new_plugins() |
1125
|
|
|
{ |
1126
|
|
|
$installed = 0; // Track how many plugins we install. |
1127
|
|
|
|
1128
|
|
|
if (file_exists(Config::get('plugin_dir'))) { |
1129
|
|
|
$plugin_files = scandir(Config::get('plugin_dir')); |
1130
|
|
|
foreach ($plugin_files as $name) { |
1131
|
|
|
if (is_dir(Config::get('plugin_dir') . '/' . $name)) { |
1132
|
|
|
if ($name != '.' && $name != '..') { |
1133
|
|
|
if (is_file(Config::get('plugin_dir') . '/' . $name . '/' . $name . '.php') && is_file(Config::get('plugin_dir') . '/' . $name . '/' . $name . '.inc.php')) { |
1134
|
|
|
$plugin_id = dbFetchRow("SELECT `plugin_id` FROM `plugins` WHERE `plugin_name` = '$name'"); |
1135
|
|
|
if (empty($plugin_id)) { |
1136
|
|
|
if (dbInsert(array('plugin_name' => $name, 'plugin_active' => '0'), 'plugins')) { |
1137
|
|
|
$installed++; |
1138
|
|
|
} |
1139
|
|
|
} |
1140
|
|
|
} |
1141
|
|
|
} |
1142
|
|
|
} |
1143
|
|
|
} |
1144
|
|
|
} |
1145
|
|
|
|
1146
|
|
|
return( $installed ); |
1147
|
|
|
} |
1148
|
|
|
|
1149
|
|
|
function validate_device_id($id) |
1150
|
|
|
{ |
1151
|
|
|
if (empty($id) || !is_numeric($id)) { |
1152
|
|
|
$return = false; |
1153
|
|
|
} else { |
1154
|
|
|
$device_id = dbFetchCell("SELECT `device_id` FROM `devices` WHERE `device_id` = ?", array($id)); |
1155
|
|
|
if ($device_id == $id) { |
1156
|
|
|
$return = true; |
1157
|
|
|
} else { |
1158
|
|
|
$return = false; |
1159
|
|
|
} |
1160
|
|
|
} |
1161
|
|
|
return($return); |
1162
|
|
|
} |
1163
|
|
|
|
1164
|
|
|
// The original source of this code is from Stackoverflow (www.stackoverflow.com). |
1165
|
|
|
// http://stackoverflow.com/questions/6054033/pretty-printing-json-with-php |
1166
|
|
|
// Answer provided by stewe (http://stackoverflow.com/users/3202187/ulk200 |
1167
|
|
|
if (!defined('JSON_UNESCAPED_SLASHES')) { |
1168
|
|
|
define('JSON_UNESCAPED_SLASHES', 64); |
1169
|
|
|
} |
1170
|
|
|
if (!defined('JSON_PRETTY_PRINT')) { |
1171
|
|
|
define('JSON_PRETTY_PRINT', 128); |
1172
|
|
|
} |
1173
|
|
|
if (!defined('JSON_UNESCAPED_UNICODE')) { |
1174
|
|
|
define('JSON_UNESCAPED_UNICODE', 256); |
1175
|
|
|
} |
1176
|
|
|
|
1177
|
|
|
function _json_encode($data, $options = 448) |
1178
|
|
|
{ |
1179
|
|
|
if (version_compare(PHP_VERSION, '5.4', '>=')) { |
1180
|
|
|
return json_encode($data, $options); |
1181
|
|
|
} else { |
1182
|
|
|
return _json_format(json_encode($data), $options); |
1183
|
|
|
} |
1184
|
|
|
} |
1185
|
|
|
|
1186
|
|
|
function _json_format($json, $options = 448) |
1187
|
|
|
{ |
1188
|
|
|
$prettyPrint = (bool) ($options & JSON_PRETTY_PRINT); |
1189
|
|
|
$unescapeUnicode = (bool) ($options & JSON_UNESCAPED_UNICODE); |
1190
|
|
|
$unescapeSlashes = (bool) ($options & JSON_UNESCAPED_SLASHES); |
1191
|
|
|
|
1192
|
|
|
if (!$prettyPrint && !$unescapeUnicode && !$unescapeSlashes) { |
1193
|
|
|
return $json; |
1194
|
|
|
} |
1195
|
|
|
|
1196
|
|
|
$result = ''; |
1197
|
|
|
$pos = 0; |
1198
|
|
|
$strLen = strlen($json); |
1199
|
|
|
$indentStr = ' '; |
1200
|
|
|
$newLine = "\n"; |
1201
|
|
|
$outOfQuotes = true; |
1202
|
|
|
$buffer = ''; |
1203
|
|
|
$noescape = true; |
1204
|
|
|
|
1205
|
|
|
for ($i = 0; $i < $strLen; $i++) { |
1206
|
|
|
// Grab the next character in the string |
1207
|
|
|
$char = substr($json, $i, 1); |
1208
|
|
|
|
1209
|
|
|
// Are we inside a quoted string? |
1210
|
|
|
if ('"' === $char && $noescape) { |
1211
|
|
|
$outOfQuotes = !$outOfQuotes; |
|
|
|
|
1212
|
|
|
} |
1213
|
|
|
|
1214
|
|
|
if (!$outOfQuotes) { |
1215
|
|
|
$buffer .= $char; |
1216
|
|
|
$noescape = '\\' === $char ? !$noescape : true; |
1217
|
|
|
continue; |
1218
|
|
|
} elseif ('' !== $buffer) { |
1219
|
|
|
if ($unescapeSlashes) { |
1220
|
|
|
$buffer = str_replace('\\/', '/', $buffer); |
1221
|
|
|
} |
1222
|
|
|
|
1223
|
|
|
if ($unescapeUnicode && function_exists('mb_convert_encoding')) { |
1224
|
|
|
// http://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha |
1225
|
|
|
$buffer = preg_replace_callback( |
1226
|
|
|
'/\\\\u([0-9a-f]{4})/i', |
1227
|
|
|
function ($match) { |
1228
|
|
|
return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE'); |
1229
|
|
|
}, |
1230
|
|
|
$buffer |
1231
|
|
|
); |
1232
|
|
|
} |
1233
|
|
|
|
1234
|
|
|
$result .= $buffer . $char; |
1235
|
|
|
$buffer = ''; |
1236
|
|
|
continue; |
1237
|
|
|
} elseif (false !== strpos(" \t\r\n", $char)) { |
1238
|
|
|
continue; |
1239
|
|
|
} |
1240
|
|
|
|
1241
|
|
|
if (':' === $char) { |
1242
|
|
|
// Add a space after the : character |
1243
|
|
|
$char .= ' '; |
1244
|
|
|
} elseif (('}' === $char || ']' === $char)) { |
1245
|
|
|
$pos--; |
1246
|
|
|
$prevChar = substr($json, $i - 1, 1); |
1247
|
|
|
|
1248
|
|
|
if ('{' !== $prevChar && '[' !== $prevChar) { |
1249
|
|
|
// If this character is the end of an element, |
1250
|
|
|
// output a new line and indent the next line |
1251
|
|
|
$result .= $newLine; |
1252
|
|
|
for ($j = 0; $j < $pos; $j++) { |
1253
|
|
|
$result .= $indentStr; |
1254
|
|
|
} |
1255
|
|
|
} else { |
1256
|
|
|
// Collapse empty {} and [] |
|
|
|
|
1257
|
|
|
$result = rtrim($result) . "\n\n" . $indentStr; |
1258
|
|
|
} |
1259
|
|
|
} |
1260
|
|
|
|
1261
|
|
|
$result .= $char; |
1262
|
|
|
|
1263
|
|
|
// If the last character was the beginning of an element, |
1264
|
|
|
// output a new line and indent the next line |
1265
|
|
|
if (',' === $char || '{' === $char || '[' === $char) { |
1266
|
|
|
$result .= $newLine; |
1267
|
|
|
|
1268
|
|
|
if ('{' === $char || '[' === $char) { |
1269
|
|
|
$pos++; |
1270
|
|
|
} |
1271
|
|
|
|
1272
|
|
|
for ($j = 0; $j < $pos; $j++) { |
1273
|
|
|
$result .= $indentStr; |
1274
|
|
|
} |
1275
|
|
|
} |
1276
|
|
|
} |
1277
|
|
|
// If buffer not empty after formating we have an unclosed quote |
1278
|
|
|
if (strlen($buffer) > 0) { |
1279
|
|
|
//json is incorrectly formatted |
1280
|
|
|
$result = false; |
1281
|
|
|
} |
1282
|
|
|
|
1283
|
|
|
return $result; |
1284
|
|
|
} |
1285
|
|
|
|
1286
|
|
|
function convert_delay($delay) |
1287
|
|
|
{ |
1288
|
|
|
$delay = preg_replace('/\s/', '', $delay); |
1289
|
|
|
if (strstr($delay, 'm', true)) { |
1290
|
|
|
$delay_sec = $delay * 60; |
1291
|
|
|
} elseif (strstr($delay, 'h', true)) { |
1292
|
|
|
$delay_sec = $delay * 3600; |
1293
|
|
|
} elseif (strstr($delay, 'd', true)) { |
1294
|
|
|
$delay_sec = $delay * 86400; |
1295
|
|
|
} elseif (is_numeric($delay)) { |
1296
|
|
|
$delay_sec = $delay; |
1297
|
|
|
} else { |
1298
|
|
|
$delay_sec = 300; |
1299
|
|
|
} |
1300
|
|
|
return($delay_sec); |
1301
|
|
|
} |
1302
|
|
|
|
1303
|
|
|
function guidv4($data) |
1304
|
|
|
{ |
1305
|
|
|
// http://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid#15875555 |
1306
|
|
|
// From: Jack http://stackoverflow.com/users/1338292/ja%CD%A2ck |
1307
|
|
|
assert(strlen($data) == 16); |
1308
|
|
|
|
1309
|
|
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100 |
1310
|
|
|
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 |
1311
|
|
|
|
1312
|
|
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); |
1313
|
|
|
} |
1314
|
|
|
|
1315
|
|
|
/** |
1316
|
|
|
* @param $curl |
1317
|
|
|
*/ |
1318
|
|
|
function set_curl_proxy($curl) |
1319
|
|
|
{ |
1320
|
|
|
$proxy = get_proxy(); |
1321
|
|
|
|
1322
|
|
|
$tmp = rtrim($proxy, "/"); |
|
|
|
|
1323
|
|
|
$proxy = str_replace(array("http://", "https://"), "", $tmp); |
1324
|
|
|
if (!empty($proxy)) { |
1325
|
|
|
curl_setopt($curl, CURLOPT_PROXY, $proxy); |
1326
|
|
|
} |
1327
|
|
|
} |
1328
|
|
|
|
1329
|
|
|
/** |
1330
|
|
|
* Return the proxy url |
1331
|
|
|
* |
1332
|
|
|
* @return array|bool|false|string |
1333
|
|
|
*/ |
1334
|
|
|
function get_proxy() |
1335
|
|
|
{ |
1336
|
|
|
if (getenv('http_proxy')) { |
1337
|
|
|
return getenv('http_proxy'); |
1338
|
|
|
} elseif (getenv('https_proxy')) { |
1339
|
|
|
return getenv('https_proxy'); |
1340
|
|
|
} elseif ($callback_proxy = Config::get('callback_proxy')) { |
1341
|
|
|
return $callback_proxy; |
1342
|
|
|
} elseif ($http_proxy = Config::get('http_proxy')) { |
1343
|
|
|
return $http_proxy; |
1344
|
|
|
} |
1345
|
|
|
return false; |
1346
|
|
|
} |
1347
|
|
|
|
1348
|
|
|
function target_to_id($target) |
1349
|
|
|
{ |
1350
|
|
|
if ($target[0].$target[1] == "g:") { |
1351
|
|
|
$target = "g".dbFetchCell('SELECT id FROM device_groups WHERE name = ?', array(substr($target, 2))); |
1352
|
|
|
} else { |
1353
|
|
|
$target = dbFetchCell('SELECT device_id FROM devices WHERE hostname = ?', array($target)); |
1354
|
|
|
} |
1355
|
|
|
return $target; |
1356
|
|
|
} |
1357
|
|
|
|
1358
|
|
|
function id_to_target($id) |
1359
|
|
|
{ |
1360
|
|
|
if ($id[0] == "g") { |
1361
|
|
|
$id = 'g:'.dbFetchCell("SELECT name FROM device_groups WHERE id = ?", array(substr($id, 1))); |
1362
|
|
|
} else { |
1363
|
|
|
$id = dbFetchCell("SELECT hostname FROM devices WHERE device_id = ?", array($id)); |
1364
|
|
|
} |
1365
|
|
|
return $id; |
1366
|
|
|
} |
1367
|
|
|
|
1368
|
|
|
function first_oid_match($device, $list) |
1369
|
|
|
{ |
1370
|
|
|
foreach ($list as $item) { |
1371
|
|
|
$tmp = trim(snmp_get($device, $item, "-Ovq"), '" '); |
|
|
|
|
1372
|
|
|
if (!empty($tmp)) { |
1373
|
|
|
return $tmp; |
1374
|
|
|
} |
1375
|
|
|
} |
1376
|
|
|
} |
1377
|
|
|
|
1378
|
|
|
|
1379
|
|
|
function fix_integer_value($value) |
1380
|
|
|
{ |
1381
|
|
|
if ($value < 0) { |
1382
|
|
|
$return = 4294967296+$value; |
1383
|
|
|
} else { |
1384
|
|
|
$return = $value; |
1385
|
|
|
} |
1386
|
|
|
return $return; |
1387
|
|
|
} |
1388
|
|
|
|
1389
|
|
|
/** |
1390
|
|
|
* Find a device that has this IP. Checks ipv4_addresses and ipv6_addresses tables. |
1391
|
|
|
* |
1392
|
|
|
* @param string $ip |
1393
|
|
|
* @return \App\Models\Device|false |
1394
|
|
|
*/ |
1395
|
|
|
function device_has_ip($ip) |
1396
|
|
|
{ |
1397
|
|
|
if (IPv6::isValid($ip)) { |
1398
|
|
|
$ip_address = \App\Models\Ipv6Address::query() |
1399
|
|
|
->where('ipv6_address', IPv6::parse($ip, true)->uncompressed()) |
1400
|
|
|
->with('port.device') |
1401
|
|
|
->first(); |
1402
|
|
|
} elseif (IPv4::isValid($ip)) { |
1403
|
|
|
$ip_address = \App\Models\Ipv4Address::query() |
1404
|
|
|
->where('ipv4_address', $ip) |
1405
|
|
|
->with('port.device') |
1406
|
|
|
->first(); |
1407
|
|
|
} |
1408
|
|
|
|
1409
|
|
|
if (isset($ip_address) && $ip_address->port) { |
1410
|
|
|
return $ip_address->port->device; |
1411
|
|
|
} |
1412
|
|
|
|
1413
|
|
|
return false; // not an ipv4 or ipv6 address... |
1414
|
|
|
} |
1415
|
|
|
|
1416
|
|
|
/** |
1417
|
|
|
* Run fping against a hostname/ip in count mode and collect stats. |
1418
|
|
|
* |
1419
|
|
|
* @param string $host |
1420
|
|
|
* @param int $count (min 1) |
1421
|
|
|
* @param int $interval (min 20) |
1422
|
|
|
* @param int $timeout (not more than $interval) |
1423
|
|
|
* @param string $address_family ipv4 or ipv6 |
1424
|
|
|
* @return array |
1425
|
|
|
*/ |
1426
|
|
|
function fping($host, $count = 3, $interval = 1000, $timeout = 500, $address_family = 'ipv4') |
1427
|
|
|
{ |
1428
|
|
|
// Default to ipv4 |
1429
|
|
|
$fping_name = $address_family == 'ipv6' ? 'fping6' : 'fping'; |
1430
|
|
|
$fping_path = Config::get($fping_name, $fping_name); |
1431
|
|
|
|
1432
|
|
|
// build the parameters |
1433
|
|
|
$params = '-e -q -c ' . max($count, 1); |
1434
|
|
|
|
1435
|
|
|
$interval = max($interval, 20); |
1436
|
|
|
$params .= ' -p ' . $interval; |
1437
|
|
|
|
1438
|
|
|
$params .= ' -t ' . max($timeout, $interval); |
1439
|
|
|
|
1440
|
|
|
$cmd = "$fping_path $params $host"; |
1441
|
|
|
|
1442
|
|
|
d_echo("[FPING] $cmd\n"); |
1443
|
|
|
|
1444
|
|
|
$process = new Process($cmd); |
|
|
|
|
1445
|
|
|
$process->run(); |
1446
|
|
|
$output = $process->getErrorOutput(); |
1447
|
|
|
|
1448
|
|
|
preg_match('#= (\d+)/(\d+)/(\d+)%, min/avg/max = ([\d.]+)/([\d.]+)/([\d.]+)$#', $output, $parsed); |
1449
|
|
|
list(, $xmt, $rcv, $loss, $min, $avg, $max) = $parsed; |
1450
|
|
|
|
1451
|
|
|
if ($loss < 0) { |
1452
|
|
|
$xmt = 1; |
1453
|
|
|
$rcv = 1; |
1454
|
|
|
$loss = 100; |
1455
|
|
|
} |
1456
|
|
|
|
1457
|
|
|
$response = [ |
1458
|
|
|
'xmt' => set_numeric($xmt), |
1459
|
|
|
'rcv' => set_numeric($rcv), |
1460
|
|
|
'loss' => set_numeric($loss), |
1461
|
|
|
'min' => set_numeric($min), |
1462
|
|
|
'max' => set_numeric($max), |
1463
|
|
|
'avg' => set_numeric($avg), |
1464
|
|
|
'exitcode' => $process->getExitCode(), |
1465
|
|
|
]; |
1466
|
|
|
d_echo($response); |
1467
|
|
|
|
1468
|
|
|
return $response; |
1469
|
|
|
} |
1470
|
|
|
|
1471
|
|
|
function function_check($function) |
1472
|
|
|
{ |
1473
|
|
|
return function_exists($function); |
1474
|
|
|
} |
1475
|
|
|
|
1476
|
|
|
function force_influx_data($data) |
1477
|
|
|
{ |
1478
|
|
|
/* |
1479
|
|
|
* It is not trivial to detect if something is a float or an integer, and |
1480
|
|
|
* therefore may cause breakages on inserts. |
1481
|
|
|
* Just setting every number to a float gets around this, but may introduce |
1482
|
|
|
* inefficiencies. |
1483
|
|
|
* I've left the detection statement in there for a possible change in future, |
1484
|
|
|
* but currently everything just gets set to a float. |
1485
|
|
|
*/ |
1486
|
|
|
|
1487
|
|
|
if (is_numeric($data)) { |
1488
|
|
|
// If it is an Integer |
1489
|
|
|
if (ctype_digit($data)) { |
1490
|
|
|
return floatval($data); |
1491
|
|
|
// Else it is a float |
1492
|
|
|
} else { |
1493
|
|
|
return floatval($data); |
1494
|
|
|
} |
1495
|
|
|
} else { |
1496
|
|
|
return $data; |
1497
|
|
|
} |
1498
|
|
|
}// end force_influx_data |
1499
|
|
|
|
1500
|
|
|
/** |
1501
|
|
|
* Try to determine the address family (IPv4 or IPv6) associated with an SNMP |
1502
|
|
|
* transport specifier (like "udp", "udp6", etc.). |
1503
|
|
|
* |
1504
|
|
|
* @param string $transport The SNMP transport specifier, for example "udp", |
1505
|
|
|
* "udp6", "tcp", or "tcp6". See `man snmpcmd`, |
1506
|
|
|
* section "Agent Specification" for a full list. |
1507
|
|
|
* |
1508
|
|
|
* @return string The address family associated with the given transport |
1509
|
|
|
* specifier: 'ipv4' (or local connections not associated |
1510
|
|
|
* with an IP stack) or 'ipv6'. |
1511
|
|
|
*/ |
1512
|
|
|
function snmpTransportToAddressFamily($transport) |
1513
|
|
|
{ |
1514
|
|
|
$ipv6_snmp_transport_specifiers = ['udp6', 'udpv6', 'udpipv6', 'tcp6', 'tcpv6', 'tcpipv6']; |
1515
|
|
|
|
1516
|
|
|
if (in_array($transport, $ipv6_snmp_transport_specifiers)) { |
1517
|
|
|
return 'ipv6'; |
1518
|
|
|
} |
1519
|
|
|
|
1520
|
|
|
return 'ipv4'; |
1521
|
|
|
} |
1522
|
|
|
|
1523
|
|
|
/** |
1524
|
|
|
* Checks if the $hostname provided exists in the DB already |
1525
|
|
|
* |
1526
|
|
|
* @param string $hostname The hostname to check for |
1527
|
|
|
* @param string $sysName The sysName to check |
1528
|
|
|
* @return bool true if hostname already exists |
1529
|
|
|
* false if hostname doesn't exist |
1530
|
|
|
*/ |
1531
|
|
|
function host_exists($hostname, $sysName = null) |
1532
|
|
|
{ |
1533
|
|
|
$query = "SELECT COUNT(*) FROM `devices` WHERE `hostname`=?"; |
1534
|
|
|
$params = array($hostname); |
1535
|
|
|
|
1536
|
|
|
if (!empty($sysName) && !Config::get('allow_duplicate_sysName')) { |
1537
|
|
|
$query .= " OR `sysName`=?"; |
1538
|
|
|
$params[] = $sysName; |
1539
|
|
|
|
1540
|
|
|
if (!empty(Config::get('mydomain'))) { |
1541
|
|
|
$full_sysname = rtrim($sysName, '.') . '.' . Config::get('mydomain'); |
1542
|
|
|
$query .= " OR `sysName`=?"; |
1543
|
|
|
$params[] = $full_sysname; |
1544
|
|
|
} |
1545
|
|
|
} |
1546
|
|
|
return dbFetchCell($query, $params) > 0; |
1547
|
|
|
} |
1548
|
|
|
|
1549
|
|
|
function oxidized_reload_nodes() |
1550
|
|
|
{ |
1551
|
|
|
if (Config::get('oxidized.enabled') === true && Config::get('oxidized.reload_nodes') === true && Config::has('oxidized.url')) { |
1552
|
|
|
$oxidized_reload_url = Config::get('oxidized.url') . '/reload.json'; |
1553
|
|
|
$ch = curl_init($oxidized_reload_url); |
1554
|
|
|
|
1555
|
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 5); |
|
|
|
|
1556
|
|
|
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000); |
1557
|
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); |
1558
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
1559
|
|
|
curl_setopt($ch, CURLOPT_HEADER, 1); |
1560
|
|
|
curl_exec($ch); |
|
|
|
|
1561
|
|
|
curl_close($ch); |
|
|
|
|
1562
|
|
|
} |
1563
|
|
|
} |
1564
|
|
|
|
1565
|
|
|
/** |
1566
|
|
|
* Perform DNS lookup |
1567
|
|
|
* |
1568
|
|
|
* @param array $device Device array from database |
1569
|
|
|
* @param string $type The type of record to lookup |
1570
|
|
|
* |
1571
|
|
|
* @return string ip |
1572
|
|
|
* |
1573
|
|
|
**/ |
1574
|
|
|
function dnslookup($device, $type = false, $return = false) |
1575
|
|
|
{ |
1576
|
|
|
if (filter_var($device['hostname'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) == true || filter_var($device['hostname'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) == true) { |
1577
|
|
|
return false; |
|
|
|
|
1578
|
|
|
} |
1579
|
|
|
if (empty($type)) { |
1580
|
|
|
// We are going to use the transport to work out the record type |
1581
|
|
|
if ($device['transport'] == 'udp6' || $device['transport'] == 'tcp6') { |
1582
|
|
|
$type = DNS_AAAA; |
1583
|
|
|
$return = 'ipv6'; |
1584
|
|
|
} else { |
1585
|
|
|
$type = DNS_A; |
1586
|
|
|
$return = 'ip'; |
1587
|
|
|
} |
1588
|
|
|
} |
1589
|
|
|
if (empty($return)) { |
1590
|
|
|
return false; |
|
|
|
|
1591
|
|
|
} |
1592
|
|
|
$record = dns_get_record($device['hostname'], $type); |
|
|
|
|
1593
|
|
|
return $record[0][$return]; |
1594
|
|
|
}//end dnslookup |
1595
|
|
|
|
1596
|
|
|
|
1597
|
|
|
|
1598
|
|
|
|
1599
|
|
|
/** |
1600
|
|
|
* Run rrdtool info on a file path |
1601
|
|
|
* |
1602
|
|
|
* @param string $path Path to pass to rrdtool info |
1603
|
|
|
* @param string $stdOutput Variable to recieve the output of STDOUT |
1604
|
|
|
* @param string $stdError Variable to recieve the output of STDERR |
1605
|
|
|
* |
1606
|
|
|
* @return int exit code |
1607
|
|
|
* |
1608
|
|
|
**/ |
1609
|
|
|
|
1610
|
|
|
function rrdtest($path, &$stdOutput, &$stdError) |
1611
|
|
|
{ |
1612
|
|
|
//rrdtool info <escaped rrd path> |
1613
|
|
|
$command = Config::get('rrdtool') . ' info ' . escapeshellarg($path); |
1614
|
|
|
$process = proc_open( |
1615
|
|
|
$command, |
1616
|
|
|
array ( |
1617
|
|
|
0 => array('pipe', 'r'), |
1618
|
|
|
1 => array('pipe', 'w'), |
1619
|
|
|
2 => array('pipe', 'w'), |
1620
|
|
|
), |
1621
|
|
|
$pipes |
1622
|
|
|
); |
1623
|
|
|
|
1624
|
|
|
if (!is_resource($process)) { |
1625
|
|
|
throw new \RuntimeException('Could not create a valid process'); |
1626
|
|
|
} |
1627
|
|
|
|
1628
|
|
|
$status = proc_get_status($process); |
1629
|
|
|
while ($status['running']) { |
1630
|
|
|
usleep(2000); // Sleep 2000 microseconds or 2 milliseconds |
1631
|
|
|
$status = proc_get_status($process); |
1632
|
|
|
} |
1633
|
|
|
|
1634
|
|
|
$stdOutput = stream_get_contents($pipes[1]); |
1635
|
|
|
$stdError = stream_get_contents($pipes[2]); |
1636
|
|
|
proc_close($process); |
1637
|
|
|
return $status['exitcode']; |
1638
|
|
|
} |
1639
|
|
|
|
1640
|
|
|
/** |
1641
|
|
|
* Create a new state index. Update translations if $states is given. |
1642
|
|
|
* |
1643
|
|
|
* For for backward compatibility: |
1644
|
|
|
* Returns null if $states is empty, $state_name already exists, and contains state translations |
1645
|
|
|
* |
1646
|
|
|
* @param string $state_name the unique name for this state translation |
1647
|
|
|
* @param array $states array of states, each must contain keys: descr, graph, value, generic |
1648
|
|
|
* @return int|null |
1649
|
|
|
*/ |
1650
|
|
|
function create_state_index($state_name, $states = array()) |
1651
|
|
|
{ |
1652
|
|
|
$state_index_id = dbFetchCell('SELECT `state_index_id` FROM state_indexes WHERE state_name = ? LIMIT 1', array($state_name)); |
1653
|
|
|
if (!is_numeric($state_index_id)) { |
1654
|
|
|
$state_index_id = dbInsert(array('state_name' => $state_name), 'state_indexes'); |
1655
|
|
|
|
1656
|
|
|
// legacy code, return index so states are created |
1657
|
|
|
if (empty($states)) { |
1658
|
|
|
return $state_index_id; |
1659
|
|
|
} |
1660
|
|
|
} |
1661
|
|
|
|
1662
|
|
|
// check or synchronize states |
1663
|
|
|
if (empty($states)) { |
1664
|
|
|
$translations = dbFetchRows('SELECT * FROM `state_translations` WHERE `state_index_id` = ?', array($state_index_id)); |
1665
|
|
|
if (count($translations) == 0) { |
1666
|
|
|
// If we don't have any translations something has gone wrong so return the state_index_id so they get created. |
1667
|
|
|
return $state_index_id; |
1668
|
|
|
} |
1669
|
|
|
} else { |
1670
|
|
|
sync_sensor_states($state_index_id, $states); |
1671
|
|
|
} |
1672
|
|
|
|
1673
|
|
|
return null; |
1674
|
|
|
} |
1675
|
|
|
|
1676
|
|
|
/** |
1677
|
|
|
* Synchronize the sensor state translations with the database |
1678
|
|
|
* |
1679
|
|
|
* @param int $state_index_id index of the state |
1680
|
|
|
* @param array $states array of states, each must contain keys: descr, graph, value, generic |
1681
|
|
|
*/ |
1682
|
|
|
function sync_sensor_states($state_index_id, $states) |
1683
|
|
|
{ |
1684
|
|
|
$new_translations = array_reduce($states, function ($array, $state) use ($state_index_id) { |
1685
|
|
|
$array[$state['value']] = array( |
1686
|
|
|
'state_index_id' => $state_index_id, |
1687
|
|
|
'state_descr' => $state['descr'], |
1688
|
|
|
'state_draw_graph' => $state['graph'], |
1689
|
|
|
'state_value' => $state['value'], |
1690
|
|
|
'state_generic_value' => $state['generic'] |
1691
|
|
|
); |
1692
|
|
|
return $array; |
1693
|
|
|
}, array()); |
1694
|
|
|
|
1695
|
|
|
$existing_translations = dbFetchRows( |
1696
|
|
|
'SELECT `state_index_id`,`state_descr`,`state_draw_graph`,`state_value`,`state_generic_value` FROM `state_translations` WHERE `state_index_id`=?', |
1697
|
|
|
array($state_index_id) |
1698
|
|
|
); |
1699
|
|
|
|
1700
|
|
|
foreach ($existing_translations as $translation) { |
1701
|
|
|
$value = $translation['state_value']; |
1702
|
|
|
if (isset($new_translations[$value])) { |
1703
|
|
|
if ($new_translations[$value] != $translation) { |
1704
|
|
|
dbUpdate( |
1705
|
|
|
$new_translations[$value], |
1706
|
|
|
'state_translations', |
1707
|
|
|
'`state_index_id`=? AND `state_value`=?', |
1708
|
|
|
array($state_index_id, $value) |
1709
|
|
|
); |
1710
|
|
|
} |
1711
|
|
|
|
1712
|
|
|
// this translation is synchronized, it doesn't need to be inserted |
1713
|
|
|
unset($new_translations[$value]); |
1714
|
|
|
} else { |
1715
|
|
|
dbDelete('state_translations', '`state_index_id`=? AND `state_value`=?', array($state_index_id, $value)); |
1716
|
|
|
} |
1717
|
|
|
} |
1718
|
|
|
|
1719
|
|
|
// insert any new translations |
1720
|
|
|
dbBulkInsert($new_translations, 'state_translations'); |
1721
|
|
|
} |
1722
|
|
|
|
1723
|
|
|
function create_sensor_to_state_index($device, $state_name, $index) |
1724
|
|
|
{ |
1725
|
|
|
$sensor_entry = dbFetchRow('SELECT sensor_id FROM `sensors` WHERE `sensor_class` = ? AND `device_id` = ? AND `sensor_type` = ? AND `sensor_index` = ?', array( |
1726
|
|
|
'state', |
1727
|
|
|
$device['device_id'], |
1728
|
|
|
$state_name, |
1729
|
|
|
$index |
1730
|
|
|
)); |
1731
|
|
|
$state_indexes_entry = dbFetchRow('SELECT state_index_id FROM `state_indexes` WHERE `state_name` = ?', array( |
1732
|
|
|
$state_name |
1733
|
|
|
)); |
1734
|
|
|
if (!empty($sensor_entry['sensor_id']) && !empty($state_indexes_entry['state_index_id'])) { |
1735
|
|
|
$insert = array( |
1736
|
|
|
'sensor_id' => $sensor_entry['sensor_id'], |
1737
|
|
|
'state_index_id' => $state_indexes_entry['state_index_id'], |
1738
|
|
|
); |
1739
|
|
|
foreach ($insert as $key => $val_check) { |
1740
|
|
|
if (!isset($val_check)) { |
1741
|
|
|
unset($insert[$key]); |
1742
|
|
|
} |
1743
|
|
|
} |
1744
|
|
|
|
1745
|
|
|
dbInsert($insert, 'sensors_to_state_indexes'); |
1746
|
|
|
} |
1747
|
|
|
} |
1748
|
|
|
|
1749
|
|
|
function delta_to_bits($delta, $period) |
1750
|
|
|
{ |
1751
|
|
|
return round(($delta * 8 / $period), 2); |
1752
|
|
|
} |
1753
|
|
|
|
1754
|
|
|
function report_this($message) |
1755
|
|
|
{ |
1756
|
|
|
return '<h2>' . $message . ' Please <a href="' . Config::get('project_issues') . '">report this</a> to the ' . Config::get('project_name') . ' developers.</h2>'; |
1757
|
|
|
}//end report_this() |
1758
|
|
|
|
1759
|
|
|
function hytera_h2f($number, $nd) |
1760
|
|
|
{ |
1761
|
|
|
if (strlen(str_replace(" ", "", $number)) == 4) { |
1762
|
|
|
$hex = ''; |
1763
|
|
|
for ($i = 0; $i < strlen($number); $i++) { |
1764
|
|
|
$byte = strtoupper(dechex(ord($number{$i}))); |
1765
|
|
|
$byte = str_repeat('0', 2 - strlen($byte)).$byte; |
1766
|
|
|
$hex.=$byte." "; |
1767
|
|
|
} |
1768
|
|
|
$number = $hex; |
1769
|
|
|
unset($hex); |
1770
|
|
|
} |
1771
|
|
|
$r = ''; |
1772
|
|
|
$y = explode(' ', $number); |
1773
|
|
|
foreach ($y as $z) { |
1774
|
|
|
$r = $z . '' . $r; |
1775
|
|
|
} |
1776
|
|
|
|
1777
|
|
|
$hex = array(); |
1778
|
|
|
$number = substr($r, 0, -1); |
1779
|
|
|
//$number = str_replace(" ", "", $number); |
|
|
|
|
1780
|
|
|
for ($i=0; $i<strlen($number); $i++) { |
1781
|
|
|
$hex[]=substr($number, $i, 1); |
1782
|
|
|
} |
1783
|
|
|
|
1784
|
|
|
$dec = array(); |
1785
|
|
|
$hexCount = count($hex); |
1786
|
|
|
for ($i=0; $i<$hexCount; $i++) { |
1787
|
|
|
$dec[]=hexdec($hex[$i]); |
1788
|
|
|
} |
1789
|
|
|
|
1790
|
|
|
$binfinal = ""; |
1791
|
|
|
$decCount = count($dec); |
1792
|
|
|
for ($i=0; $i<$decCount; $i++) { |
1793
|
|
|
$binfinal.=sprintf("%04d", decbin($dec[$i])); |
1794
|
|
|
} |
1795
|
|
|
|
1796
|
|
|
$sign=substr($binfinal, 0, 1); |
1797
|
|
|
$exp=substr($binfinal, 1, 8); |
1798
|
|
|
$exp=bindec($exp); |
1799
|
|
|
$exp-=127; |
1800
|
|
|
$scibin=substr($binfinal, 9); |
1801
|
|
|
$binint=substr($scibin, 0, $exp); |
|
|
|
|
1802
|
|
|
$binpoint=substr($scibin, $exp); |
|
|
|
|
1803
|
|
|
$intnumber=bindec("1".$binint); |
1804
|
|
|
|
1805
|
|
|
$tmppoint = []; |
1806
|
|
|
for ($i=0; $i<strlen($binpoint); $i++) { |
1807
|
|
|
$tmppoint[]=substr($binpoint, $i, 1); |
1808
|
|
|
} |
1809
|
|
|
|
1810
|
|
|
$tmppoint=array_reverse($tmppoint); |
1811
|
|
|
$tpointnumber=number_format($tmppoint[0]/2, strlen($binpoint), '.', ''); |
1812
|
|
|
|
1813
|
|
|
$pointnumber = ""; |
1814
|
|
|
for ($i=1; $i<strlen($binpoint); $i++) { |
1815
|
|
|
$pointnumber=number_format($tpointnumber/2, strlen($binpoint), '.', ''); |
1816
|
|
|
$tpointnumber=$tmppoint[$i+1].substr($pointnumber, 1); |
1817
|
|
|
} |
1818
|
|
|
|
1819
|
|
|
$floatfinal=$intnumber+$pointnumber; |
1820
|
|
|
|
1821
|
|
|
if ($sign==1) { |
1822
|
|
|
$floatfinal=-$floatfinal; |
1823
|
|
|
} |
1824
|
|
|
|
1825
|
|
|
return number_format($floatfinal, $nd, '.', ''); |
1826
|
|
|
} |
1827
|
|
|
|
1828
|
|
|
/* |
1829
|
|
|
* Cisco CIMC functions |
1830
|
|
|
*/ |
1831
|
|
|
// Create an entry in the entPhysical table if it doesnt already exist. |
1832
|
|
|
function setCIMCentPhysical($location, $data, &$entphysical, &$index) |
1833
|
|
|
{ |
1834
|
|
|
// Go get the location, this will create it if it doesnt exist. |
1835
|
|
|
$entPhysicalIndex = getCIMCentPhysical($location, $entphysical, $index); |
1836
|
|
|
|
1837
|
|
|
// See if we need to update |
1838
|
|
|
$update = array(); |
1839
|
|
|
foreach ($data as $key => $value) { |
1840
|
|
|
// Is the Array(DB) value different to the supplied data |
1841
|
|
|
if ($entphysical[$location][$key] != $value) { |
1842
|
|
|
$update[$key] = $value; |
1843
|
|
|
$entphysical[$location][$key] = $value; |
1844
|
|
|
} // End if |
1845
|
|
|
} // end foreach |
1846
|
|
|
|
1847
|
|
|
// Do we need to update |
1848
|
|
|
if (count($update) > 0) { |
1849
|
|
|
dbUpdate($update, 'entPhysical', '`entPhysical_id` = ?', array($entphysical[$location]['entPhysical_id'])); |
1850
|
|
|
} |
1851
|
|
|
$entPhysicalId = $entphysical[$location]['entPhysical_id']; |
1852
|
|
|
return array($entPhysicalId, $entPhysicalIndex); |
1853
|
|
|
} |
1854
|
|
|
|
1855
|
|
|
function getCIMCentPhysical($location, &$entphysical, &$index) |
1856
|
|
|
{ |
1857
|
|
|
global $device; |
|
|
|
|
1858
|
|
|
|
1859
|
|
|
// Level 1 - Does the location exist |
1860
|
|
|
if (isset($entphysical[$location])) { |
1861
|
|
|
// Yes, return the entPhysicalIndex. |
1862
|
|
|
return $entphysical[$location]['entPhysicalIndex']; |
1863
|
|
|
} else { |
1864
|
|
|
/* |
1865
|
|
|
* No, the entry doesnt exist. |
1866
|
|
|
* Find its parent so we can create it. |
1867
|
|
|
*/ |
1868
|
|
|
|
1869
|
|
|
// Pull apart the location |
1870
|
|
|
$parts = explode('/', $location); |
1871
|
|
|
|
1872
|
|
|
// Level 2 - Are we at the root |
1873
|
|
|
if (count($parts) == 1) { |
1874
|
|
|
// Level 2 - Yes. We are the root, there is no parent |
1875
|
|
|
d_echo("ROOT - ".$location."\n"); |
1876
|
|
|
$shortlocation = $location; |
1877
|
|
|
$parent = 0; |
1878
|
|
|
} else { |
1879
|
|
|
// Level 2 - No. Need to go deeper. |
1880
|
|
|
d_echo("NON-ROOT - ".$location."\n"); |
1881
|
|
|
$shortlocation = array_pop($parts); |
1882
|
|
|
$parentlocation = implode('/', $parts); |
1883
|
|
|
d_echo("Decend - parent location: ".$parentlocation."\n"); |
1884
|
|
|
$parent = getCIMCentPhysical($parentlocation, $entphysical, $index); |
1885
|
|
|
} // end if - Level 2 |
1886
|
|
|
d_echo("Parent: ".$parent."\n"); |
1887
|
|
|
|
1888
|
|
|
// Now we have an ID, create the entry. |
1889
|
|
|
$index++; |
1890
|
|
|
$insert = array( |
1891
|
|
|
'device_id' => $device['device_id'], |
1892
|
|
|
'entPhysicalIndex' => $index, |
1893
|
|
|
'entPhysicalClass' => 'container', |
1894
|
|
|
'entPhysicalVendorType' => $location, |
1895
|
|
|
'entPhysicalName' => $shortlocation, |
1896
|
|
|
'entPhysicalContainedIn' => $parent, |
1897
|
|
|
'entPhysicalParentRelPos' => '-1', |
1898
|
|
|
); |
1899
|
|
|
|
1900
|
|
|
// Add to the DB and Array. |
1901
|
|
|
$id = dbInsert($insert, 'entPhysical'); |
1902
|
|
|
$entphysical[$location] = dbFetchRow('SELECT * FROM entPhysical WHERE entPhysical_id=?', array($id)); |
1903
|
|
|
return $index; |
1904
|
|
|
} // end if - Level 1 |
1905
|
|
|
} // end function |
1906
|
|
|
|
1907
|
|
|
|
1908
|
|
|
/* idea from http://php.net/manual/en/function.hex2bin.php comments */ |
1909
|
|
|
function hex2bin_compat($str) |
1910
|
|
|
{ |
1911
|
|
|
if (strlen($str) % 2 !== 0) { |
1912
|
|
|
trigger_error(__FUNCTION__.'(): Hexadecimal input string must have an even length', E_USER_WARNING); |
1913
|
|
|
} |
1914
|
|
|
return pack("H*", $str); |
1915
|
|
|
} |
1916
|
|
|
|
1917
|
|
|
if (!function_exists('hex2bin')) { |
1918
|
|
|
// This is only a hack |
1919
|
|
|
function hex2bin($str) |
1920
|
|
|
{ |
1921
|
|
|
return hex2bin_compat($str); |
1922
|
|
|
} |
1923
|
|
|
} |
1924
|
|
|
|
1925
|
|
|
function q_bridge_bits2indices($hex_data) |
1926
|
|
|
{ |
1927
|
|
|
/* convert hex string to an array of 1-based indices of the nonzero bits |
1928
|
|
|
* ie. '9a00' -> '100110100000' -> array(1, 4, 5, 7) |
1929
|
|
|
*/ |
1930
|
|
|
$hex_data = str_replace(' ', '', $hex_data); |
1931
|
|
|
$value = hex2bin($hex_data); |
1932
|
|
|
$length = strlen($value); |
1933
|
|
|
$indices = array(); |
1934
|
|
|
for ($i = 0; $i < $length; $i++) { |
1935
|
|
|
$byte = ord($value[$i]); |
1936
|
|
|
for ($j = 7; $j >= 0; $j--) { |
1937
|
|
|
if ($byte & (1 << $j)) { |
1938
|
|
|
$indices[] = 8*$i + 8-$j; |
1939
|
|
|
} |
1940
|
|
|
} |
1941
|
|
|
} |
1942
|
|
|
return $indices; |
1943
|
|
|
} |
1944
|
|
|
|
1945
|
|
|
/** |
1946
|
|
|
* @param array $device |
1947
|
|
|
* @param int|string $raw_value The value returned from snmp |
1948
|
|
|
* @param int $capacity the normalized capacity |
1949
|
|
|
* @return int the toner level as a percentage |
1950
|
|
|
*/ |
1951
|
|
|
function get_toner_levels($device, $raw_value, $capacity) |
1952
|
|
|
{ |
1953
|
|
|
// -3 means some toner is left |
1954
|
|
|
if ($raw_value == '-3') { |
1955
|
|
|
return 50; |
1956
|
|
|
} |
1957
|
|
|
|
1958
|
|
|
// -2 means unknown |
1959
|
|
|
if ($raw_value == '-2') { |
1960
|
|
|
return false; |
|
|
|
|
1961
|
|
|
} |
1962
|
|
|
|
1963
|
|
|
// -1 mean no restrictions |
1964
|
|
|
if ($raw_value == '-1') { |
1965
|
|
|
return 0; // FIXME: is 0 what we should return? |
1966
|
|
|
} |
1967
|
|
|
|
1968
|
|
|
// Non-standard snmp values |
1969
|
|
|
if ($device['os'] == 'ricoh' || $device['os'] == 'nrg' || $device['os'] == 'lanier') { |
1970
|
|
|
if ($raw_value == '-100') { |
1971
|
|
|
return 0; |
1972
|
|
|
} |
1973
|
|
|
} elseif ($device['os'] == 'brother') { |
1974
|
|
|
if (!str_contains($device['hardware'], 'MFC-L8850')) { |
1975
|
|
|
switch ($raw_value) { |
1976
|
|
|
case '0': |
1977
|
|
|
return 100; |
1978
|
|
|
case '1': |
1979
|
|
|
return 5; |
1980
|
|
|
case '2': |
1981
|
|
|
return 0; |
1982
|
|
|
case '3': |
1983
|
|
|
return 1; |
1984
|
|
|
} |
1985
|
|
|
} |
1986
|
|
|
} |
1987
|
|
|
|
1988
|
|
|
return round($raw_value / $capacity * 100); |
1989
|
|
|
} |
1990
|
|
|
|
1991
|
|
|
/** |
1992
|
|
|
* Intialize global stat arrays |
1993
|
|
|
*/ |
1994
|
|
|
function initStats() |
1995
|
|
|
{ |
1996
|
|
|
global $snmp_stats, $rrd_stats; |
|
|
|
|
1997
|
|
|
global $snmp_stats_last, $rrd_stats_last; |
|
|
|
|
1998
|
|
|
|
1999
|
|
|
if (!isset($snmp_stats, $rrd_stats)) { |
2000
|
|
|
$snmp_stats = array( |
2001
|
|
|
'ops' => array( |
2002
|
|
|
'snmpget' => 0, |
2003
|
|
|
'snmpgetnext' => 0, |
2004
|
|
|
'snmpwalk' => 0, |
2005
|
|
|
), |
2006
|
|
|
'time' => array( |
2007
|
|
|
'snmpget' => 0.0, |
2008
|
|
|
'snmpgetnext' => 0.0, |
2009
|
|
|
'snmpwalk' => 0.0, |
2010
|
|
|
) |
2011
|
|
|
); |
2012
|
|
|
$snmp_stats_last = $snmp_stats; |
2013
|
|
|
|
2014
|
|
|
$rrd_stats = array( |
2015
|
|
|
'ops' => array( |
2016
|
|
|
'update' => 0, |
2017
|
|
|
'create' => 0, |
2018
|
|
|
'other' => 0, |
2019
|
|
|
), |
2020
|
|
|
'time' => array( |
2021
|
|
|
'update' => 0.0, |
2022
|
|
|
'create' => 0.0, |
2023
|
|
|
'other' => 0.0, |
2024
|
|
|
), |
2025
|
|
|
); |
2026
|
|
|
$rrd_stats_last = $rrd_stats; |
2027
|
|
|
} |
2028
|
|
|
} |
2029
|
|
|
|
2030
|
|
|
/** |
2031
|
|
|
* Print out the stats totals since the last time this function was called |
2032
|
|
|
* |
2033
|
|
|
* @param bool $update_only Only update the stats checkpoint, don't print them |
2034
|
|
|
*/ |
2035
|
|
|
function printChangedStats($update_only = false) |
2036
|
|
|
{ |
2037
|
|
|
global $snmp_stats, $db_stats, $rrd_stats; |
|
|
|
|
2038
|
|
|
global $snmp_stats_last, $db_stats_last, $rrd_stats_last; |
|
|
|
|
2039
|
|
|
|
2040
|
|
|
if (!$update_only) { |
2041
|
|
|
printf( |
2042
|
|
|
">> SNMP: [%d/%.2fs] MySQL: [%d/%.2fs] RRD: [%d/%.2fs]\n", |
2043
|
|
|
array_sum($snmp_stats['ops']) - array_sum($snmp_stats_last['ops']), |
2044
|
|
|
array_sum($snmp_stats['time']) - array_sum($snmp_stats_last['time']), |
2045
|
|
|
array_sum($db_stats['ops']) - array_sum($db_stats_last['ops']), |
2046
|
|
|
array_sum($db_stats['time']) - array_sum($db_stats_last['time']), |
2047
|
|
|
array_sum($rrd_stats['ops']) - array_sum($rrd_stats_last['ops']), |
2048
|
|
|
array_sum($rrd_stats['time']) - array_sum($rrd_stats_last['time']) |
2049
|
|
|
); |
2050
|
|
|
} |
2051
|
|
|
|
2052
|
|
|
// make a new checkpoint |
2053
|
|
|
$snmp_stats_last = $snmp_stats; |
2054
|
|
|
$db_stats_last = $db_stats; |
2055
|
|
|
$rrd_stats_last = $rrd_stats; |
2056
|
|
|
} |
2057
|
|
|
|
2058
|
|
|
/** |
2059
|
|
|
* Print global stat arrays |
2060
|
|
|
*/ |
2061
|
|
|
function printStats() |
2062
|
|
|
{ |
2063
|
|
|
global $snmp_stats, $db_stats, $rrd_stats; |
|
|
|
|
2064
|
|
|
|
2065
|
|
|
if ($snmp_stats) { |
2066
|
|
|
printf( |
2067
|
|
|
"SNMP [%d/%.2fs]: Get[%d/%.2fs] Getnext[%d/%.2fs] Walk[%d/%.2fs]\n", |
2068
|
|
|
array_sum($snmp_stats['ops']), |
2069
|
|
|
array_sum($snmp_stats['time']), |
2070
|
|
|
$snmp_stats['ops']['snmpget'], |
2071
|
|
|
$snmp_stats['time']['snmpget'], |
2072
|
|
|
$snmp_stats['ops']['snmpgetnext'], |
2073
|
|
|
$snmp_stats['time']['snmpgetnext'], |
2074
|
|
|
$snmp_stats['ops']['snmpwalk'], |
2075
|
|
|
$snmp_stats['time']['snmpwalk'] |
2076
|
|
|
); |
2077
|
|
|
} |
2078
|
|
|
|
2079
|
|
|
if ($db_stats) { |
2080
|
|
|
printf( |
2081
|
|
|
"MySQL [%d/%.2fs]: Cell[%d/%.2fs] Row[%d/%.2fs] Rows[%d/%.2fs] Column[%d/%.2fs] Update[%d/%.2fs] Insert[%d/%.2fs] Delete[%d/%.2fs]\n", |
2082
|
|
|
array_sum($db_stats['ops']), |
2083
|
|
|
array_sum($db_stats['time']), |
2084
|
|
|
$db_stats['ops']['fetchcell'], |
2085
|
|
|
$db_stats['time']['fetchcell'], |
2086
|
|
|
$db_stats['ops']['fetchrow'], |
2087
|
|
|
$db_stats['time']['fetchrow'], |
2088
|
|
|
$db_stats['ops']['fetchrows'], |
2089
|
|
|
$db_stats['time']['fetchrows'], |
2090
|
|
|
$db_stats['ops']['fetchcolumn'], |
2091
|
|
|
$db_stats['time']['fetchcolumn'], |
2092
|
|
|
$db_stats['ops']['update'], |
2093
|
|
|
$db_stats['time']['update'], |
2094
|
|
|
$db_stats['ops']['insert'], |
2095
|
|
|
$db_stats['time']['insert'], |
2096
|
|
|
$db_stats['ops']['delete'], |
2097
|
|
|
$db_stats['time']['delete'] |
2098
|
|
|
); |
2099
|
|
|
} |
2100
|
|
|
|
2101
|
|
|
if ($rrd_stats) { |
2102
|
|
|
printf( |
2103
|
|
|
"RRD [%d/%.2fs]: Update[%d/%.2fs] Create [%d/%.2fs] Other[%d/%.2fs]\n", |
2104
|
|
|
array_sum($rrd_stats['ops']), |
2105
|
|
|
array_sum($rrd_stats['time']), |
2106
|
|
|
$rrd_stats['ops']['update'], |
2107
|
|
|
$rrd_stats['time']['update'], |
2108
|
|
|
$rrd_stats['ops']['create'], |
2109
|
|
|
$rrd_stats['time']['create'], |
2110
|
|
|
$rrd_stats['ops']['other'], |
2111
|
|
|
$rrd_stats['time']['other'] |
2112
|
|
|
); |
2113
|
|
|
} |
2114
|
|
|
} |
2115
|
|
|
|
2116
|
|
|
/** |
2117
|
|
|
* Update statistics for rrd operations |
2118
|
|
|
* |
2119
|
|
|
* @param string $stat create, update, and other |
2120
|
|
|
* @param float $start_time The time the operation started with 'microtime(true)' |
2121
|
|
|
* @return float The calculated run time |
2122
|
|
|
*/ |
2123
|
|
|
function recordRrdStatistic($stat, $start_time) |
2124
|
|
|
{ |
2125
|
|
|
global $rrd_stats; |
|
|
|
|
2126
|
|
|
initStats(); |
2127
|
|
|
|
2128
|
|
|
$stat = ($stat == 'update' || $stat == 'create') ? $stat : 'other'; |
2129
|
|
|
|
2130
|
|
|
$runtime = microtime(true) - $start_time; |
2131
|
|
|
$rrd_stats['ops'][$stat]++; |
2132
|
|
|
$rrd_stats['time'][$stat] += $runtime; |
2133
|
|
|
|
2134
|
|
|
return $runtime; |
2135
|
|
|
} |
2136
|
|
|
|
2137
|
|
|
/** |
2138
|
|
|
* @param string $stat snmpget, snmpwalk |
2139
|
|
|
* @param float $start_time The time the operation started with 'microtime(true)' |
2140
|
|
|
* @return float The calculated run time |
2141
|
|
|
*/ |
2142
|
|
|
function recordSnmpStatistic($stat, $start_time) |
2143
|
|
|
{ |
2144
|
|
|
global $snmp_stats; |
|
|
|
|
2145
|
|
|
initStats(); |
2146
|
|
|
|
2147
|
|
|
$runtime = microtime(true) - $start_time; |
2148
|
|
|
$snmp_stats['ops'][$stat]++; |
2149
|
|
|
$snmp_stats['time'][$stat] += $runtime; |
2150
|
|
|
return $runtime; |
2151
|
|
|
} |
2152
|
|
|
|
2153
|
|
|
function runTraceroute($device) |
2154
|
|
|
{ |
2155
|
|
|
$address_family = snmpTransportToAddressFamily($device['transport']); |
2156
|
|
|
$trace_name = $address_family == 'ipv6' ? 'traceroute6' : 'traceroute'; |
2157
|
|
|
$trace_path = Config::get($trace_name, $trace_name); |
2158
|
|
|
$process = new Process([$trace_path, '-q', '1', '-w', '1', $device['hostname']]); |
2159
|
|
|
$process->run(); |
2160
|
|
|
if ($process->isSuccessful()) { |
2161
|
|
|
return ['traceroute' => $process->getOutput()]; |
2162
|
|
|
} |
2163
|
|
|
return ['output' => $process->getErrorOutput()]; |
2164
|
|
|
} |
2165
|
|
|
|
2166
|
|
|
/** |
2167
|
|
|
* @param $device |
2168
|
|
|
* @param bool $record_perf |
2169
|
|
|
* @return array |
2170
|
|
|
*/ |
2171
|
|
|
function device_is_up($device, $record_perf = false) |
2172
|
|
|
{ |
2173
|
|
|
$address_family = snmpTransportToAddressFamily($device['transport']); |
2174
|
|
|
$ping_response = isPingable($device['hostname'], $address_family, $device['attribs']); |
2175
|
|
|
$device_perf = $ping_response['db']; |
2176
|
|
|
$device_perf['device_id'] = $device['device_id']; |
2177
|
|
|
$device_perf['timestamp'] = array('NOW()'); |
2178
|
|
|
|
2179
|
|
|
if ($record_perf === true && can_ping_device($device['attribs'])) { |
2180
|
|
|
$trace_debug = []; |
2181
|
|
|
if ($ping_response['result'] === false && Config::get('debug.run_trace', false)) { |
2182
|
|
|
$trace_debug = runTraceroute($device); |
2183
|
|
|
} |
2184
|
|
|
$device_perf['debug'] = json_encode($trace_debug); |
2185
|
|
|
dbInsert($device_perf, 'device_perf'); |
2186
|
|
|
} |
2187
|
|
|
$response = array(); |
2188
|
|
|
$response['ping_time'] = $ping_response['last_ping_timetaken']; |
2189
|
|
|
if ($ping_response['result']) { |
2190
|
|
|
if ($device['snmp_disable'] || isSNMPable($device)) { |
2191
|
|
|
$response['status'] = '1'; |
2192
|
|
|
$response['status_reason'] = ''; |
2193
|
|
|
} else { |
2194
|
|
|
echo 'SNMP Unreachable'; |
2195
|
|
|
$response['status'] = '0'; |
2196
|
|
|
$response['status_reason'] = 'snmp'; |
2197
|
|
|
} |
2198
|
|
|
} else { |
2199
|
|
|
echo 'Unpingable'; |
2200
|
|
|
$response['status'] = '0'; |
2201
|
|
|
$response['status_reason'] = 'icmp'; |
2202
|
|
|
} |
2203
|
|
|
|
2204
|
|
|
if ($device['status'] != $response['status'] || $device['status_reason'] != $response['status_reason']) { |
2205
|
|
|
dbUpdate( |
2206
|
|
|
array('status' => $response['status'], 'status_reason' => $response['status_reason']), |
2207
|
|
|
'devices', |
2208
|
|
|
'device_id=?', |
2209
|
|
|
array($device['device_id']) |
2210
|
|
|
); |
2211
|
|
|
|
2212
|
|
|
if ($response['status']) { |
2213
|
|
|
$type = 'up'; |
2214
|
|
|
$reason = $device['status_reason']; |
2215
|
|
|
} else { |
2216
|
|
|
$type = 'down'; |
2217
|
|
|
$reason = $response['status_reason']; |
2218
|
|
|
} |
2219
|
|
|
|
2220
|
|
|
log_event('Device status changed to ' . ucfirst($type) . " from $reason check.", $device, $type); |
2221
|
|
|
} |
2222
|
|
|
return $response; |
2223
|
|
|
} |
2224
|
|
|
|
2225
|
|
|
function update_device_logo(&$device) |
2226
|
|
|
{ |
2227
|
|
|
$icon = getImageName($device, false); |
2228
|
|
|
if ($icon != $device['icon']) { |
2229
|
|
|
log_event('Device Icon changed ' . $device['icon'] . " => $icon", $device, 'system', 3); |
2230
|
|
|
$device['icon'] = $icon; |
2231
|
|
|
dbUpdate(array('icon' => $icon), 'devices', 'device_id=?', array($device['device_id'])); |
2232
|
|
|
echo "Changed Icon! : $icon\n"; |
2233
|
|
|
} |
2234
|
|
|
} |
2235
|
|
|
|
2236
|
|
|
/** |
2237
|
|
|
* Function to generate PeeringDB Cache |
2238
|
|
|
*/ |
2239
|
|
|
function cache_peeringdb() |
2240
|
|
|
{ |
2241
|
|
|
if (Config::get('peeringdb.enabled') === true) { |
2242
|
|
|
$peeringdb_url = 'https://peeringdb.com/api'; |
2243
|
|
|
// We cache for 71 hours |
2244
|
|
|
$cached = dbFetchCell("SELECT count(*) FROM `pdb_ix` WHERE (UNIX_TIMESTAMP() - timestamp) < 255600"); |
2245
|
|
|
if ($cached == 0) { |
|
|
|
|
2246
|
|
|
$rand = rand(3, 30); |
2247
|
|
|
echo "No cached PeeringDB data found, sleeping for $rand seconds" . PHP_EOL; |
2248
|
|
|
sleep($rand); |
2249
|
|
|
$peer_keep = []; |
2250
|
|
|
$ix_keep = []; |
2251
|
|
|
foreach (dbFetchRows("SELECT `bgpLocalAs` FROM `devices` WHERE `disabled` = 0 AND `ignore` = 0 AND `bgpLocalAs` > 0 AND (`bgpLocalAs` < 64512 OR `bgpLocalAs` > 65535) AND `bgpLocalAs` < 4200000000 GROUP BY `bgpLocalAs`") as $as) { |
2252
|
|
|
$asn = $as['bgpLocalAs']; |
2253
|
|
|
$get = Requests::get($peeringdb_url . '/net?depth=2&asn=' . $asn, array(), array('proxy' => get_proxy())); |
2254
|
|
|
$json_data = $get->body; |
2255
|
|
|
$data = json_decode($json_data); |
2256
|
|
|
$ixs = $data->{'data'}{0}->{'netixlan_set'}; |
2257
|
|
|
foreach ($ixs as $ix) { |
2258
|
|
|
$ixid = $ix->{'ix_id'}; |
2259
|
|
|
$tmp_ix = dbFetchRow("SELECT * FROM `pdb_ix` WHERE `ix_id` = ? AND asn = ?", array($ixid, $asn)); |
2260
|
|
|
if ($tmp_ix) { |
2261
|
|
|
$pdb_ix_id = $tmp_ix['pdb_ix_id']; |
2262
|
|
|
$update = array('name' => $ix->{'name'}, 'timestamp' => time()); |
2263
|
|
|
dbUpdate($update, 'pdb_ix', '`ix_id` = ? AND `asn` = ?', array($ixid, $asn)); |
2264
|
|
|
} else { |
2265
|
|
|
$insert = array( |
2266
|
|
|
'ix_id' => $ixid, |
2267
|
|
|
'name' => $ix->{'name'}, |
2268
|
|
|
'asn' => $asn, |
2269
|
|
|
'timestamp' => time() |
2270
|
|
|
); |
2271
|
|
|
$pdb_ix_id = dbInsert($insert, 'pdb_ix'); |
2272
|
|
|
} |
2273
|
|
|
$ix_keep[] = $pdb_ix_id; |
2274
|
|
|
$get_ix = Requests::get("$peeringdb_url/netixlan?ix_id=$ixid", array(), array('proxy' => get_proxy())); |
2275
|
|
|
$ix_json = $get_ix->body; |
2276
|
|
|
$ix_data = json_decode($ix_json); |
2277
|
|
|
$peers = $ix_data->{'data'}; |
2278
|
|
|
foreach ($peers as $index => $peer) { |
2279
|
|
|
$peer_name = get_astext($peer->{'asn'}); |
2280
|
|
|
$tmp_peer = dbFetchRow("SELECT * FROM `pdb_ix_peers` WHERE `peer_id` = ? AND `ix_id` = ?", array($peer->{'id'}, $ixid)); |
2281
|
|
|
if ($tmp_peer) { |
2282
|
|
|
$peer_keep[] = $tmp_peer['pdb_ix_peers_id']; |
2283
|
|
|
$update = array( |
2284
|
|
|
'remote_asn' => $peer->{'asn'}, |
2285
|
|
|
'remote_ipaddr4' => $peer->{'ipaddr4'}, |
2286
|
|
|
'remote_ipaddr6' => $peer->{'ipaddr6'}, |
2287
|
|
|
'name' => $peer_name, |
2288
|
|
|
); |
2289
|
|
|
dbUpdate($update, 'pdb_ix_peers', '`pdb_ix_peers_id` = ?', array($tmp_peer['pdb_ix_peers_id'])); |
2290
|
|
|
} else { |
2291
|
|
|
$peer_insert = array( |
2292
|
|
|
'ix_id' => $ixid, |
2293
|
|
|
'peer_id' => $peer->{'id'}, |
2294
|
|
|
'remote_asn' => $peer->{'asn'}, |
2295
|
|
|
'remote_ipaddr4' => $peer->{'ipaddr4'}, |
2296
|
|
|
'remote_ipaddr6' => $peer->{'ipaddr6'}, |
2297
|
|
|
'name' => $peer_name, |
2298
|
|
|
'timestamp' => time() |
2299
|
|
|
); |
2300
|
|
|
$peer_keep[] = dbInsert($peer_insert, 'pdb_ix_peers'); |
2301
|
|
|
} |
2302
|
|
|
} |
2303
|
|
|
} |
2304
|
|
|
} |
2305
|
|
|
|
2306
|
|
|
// cleanup |
2307
|
|
|
if (empty($peer_keep)) { |
2308
|
|
|
dbDelete('pdb_ix_peers'); |
2309
|
|
|
} else { |
2310
|
|
|
dbDelete('pdb_ix_peers', "`pdb_ix_peers_id` NOT IN " . dbGenPlaceholders(count($peer_keep)), $peer_keep); |
2311
|
|
|
} |
2312
|
|
|
if (empty($ix_keep)) { |
2313
|
|
|
dbDelete('pdb_ix'); |
2314
|
|
|
} else { |
2315
|
|
|
dbDelete('pdb_ix', "`pdb_ix_id` NOT IN " . dbGenPlaceholders(count($ix_keep)), $ix_keep); |
2316
|
|
|
} |
2317
|
|
|
} else { |
2318
|
|
|
echo "Cached PeeringDB data found....." . PHP_EOL; |
2319
|
|
|
} |
2320
|
|
|
} else { |
2321
|
|
|
echo 'Peering DB integration disabled' . PHP_EOL; |
2322
|
|
|
} |
2323
|
|
|
} |
2324
|
|
|
|
2325
|
|
|
/** |
2326
|
|
|
* Dump the database schema to an array. |
2327
|
|
|
* The top level will be a list of tables |
2328
|
|
|
* Each table contains the keys Columns and Indexes. |
2329
|
|
|
* |
2330
|
|
|
* Each entry in the Columns array contains these keys: Field, Type, Null, Default, Extra |
2331
|
|
|
* Each entry in the Indexes array contains these keys: Name, Columns(array), Unique |
2332
|
|
|
* |
2333
|
|
|
* @return array |
2334
|
|
|
*/ |
2335
|
|
|
function dump_db_schema() |
2336
|
|
|
{ |
2337
|
|
|
$output = []; |
2338
|
|
|
$db_name = dbFetchCell('SELECT DATABASE()'); |
2339
|
|
|
|
2340
|
|
|
foreach (dbFetchRows("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '$db_name' ORDER BY TABLE_NAME;") as $table) { |
2341
|
|
|
$table = $table['TABLE_NAME']; |
2342
|
|
|
foreach (dbFetchRows("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '$db_name' AND TABLE_NAME='$table'") as $data) { |
2343
|
|
|
$def = [ |
2344
|
|
|
'Field' => $data['COLUMN_NAME'], |
2345
|
|
|
'Type' => $data['COLUMN_TYPE'], |
2346
|
|
|
'Null' => $data['IS_NULLABLE'] === 'YES', |
2347
|
|
|
'Extra' => str_replace('current_timestamp()', 'CURRENT_TIMESTAMP', $data['EXTRA']), |
2348
|
|
|
]; |
2349
|
|
|
|
2350
|
|
|
if (isset($data['COLUMN_DEFAULT']) && $data['COLUMN_DEFAULT'] != 'NULL') { |
2351
|
|
|
$default = trim($data['COLUMN_DEFAULT'], "'"); |
2352
|
|
|
$def['Default'] = str_replace('current_timestamp()', 'CURRENT_TIMESTAMP', $default); |
2353
|
|
|
} |
2354
|
|
|
|
2355
|
|
|
$output[$table]['Columns'][] = $def; |
2356
|
|
|
} |
2357
|
|
|
|
2358
|
|
|
foreach (dbFetchRows("SHOW INDEX FROM `$table`") as $key) { |
2359
|
|
|
$key_name = $key['Key_name']; |
2360
|
|
|
if (isset($output[$table]['Indexes'][$key_name])) { |
2361
|
|
|
$output[$table]['Indexes'][$key_name]['Columns'][] = $key['Column_name']; |
2362
|
|
|
} else { |
2363
|
|
|
$output[$table]['Indexes'][$key_name] = [ |
2364
|
|
|
'Name' => $key['Key_name'], |
2365
|
|
|
'Columns' => [$key['Column_name']], |
2366
|
|
|
'Unique' => !$key['Non_unique'], |
2367
|
|
|
'Type' => $key['Index_type'], |
2368
|
|
|
]; |
2369
|
|
|
} |
2370
|
|
|
} |
2371
|
|
|
|
2372
|
|
|
$create = dbFetchRow("SHOW CREATE TABLE `$table`"); |
2373
|
|
|
if (isset($create['Create Table'])) { |
2374
|
|
|
$constraint_regex = '/CONSTRAINT `(?<name>[A-Za-z_0-9]+)` FOREIGN KEY \(`(?<foreign_key>[A-Za-z_0-9]+)`\) REFERENCES `(?<table>[A-Za-z_0-9]+)` \(`(?<key>[A-Za-z_0-9]+)`\) ?(?<extra>[ A-Z]+)?/'; |
2375
|
|
|
$constraint_count = preg_match_all($constraint_regex, $create['Create Table'], $constraints); |
2376
|
|
|
for ($i = 0; $i < $constraint_count; $i++) { |
2377
|
|
|
$constraint_name = $constraints['name'][$i]; |
2378
|
|
|
$output[$table]['Constraints'][$constraint_name] = [ |
2379
|
|
|
'name' => $constraint_name, |
2380
|
|
|
'foreign_key' => $constraints['foreign_key'][$i], |
2381
|
|
|
'table' => $constraints['table'][$i], |
2382
|
|
|
'key' => $constraints['key'][$i], |
2383
|
|
|
'extra' => $constraints['extra'][$i], |
2384
|
|
|
]; |
2385
|
|
|
} |
2386
|
|
|
} |
2387
|
|
|
} |
2388
|
|
|
|
2389
|
|
|
return $output; |
2390
|
|
|
} |
2391
|
|
|
|
2392
|
|
|
|
2393
|
|
|
|
2394
|
|
|
|
2395
|
|
|
|
2396
|
|
|
|
2397
|
|
|
/** |
2398
|
|
|
* Get an array of the schema files. |
2399
|
|
|
* schema_version => full_file_name |
2400
|
|
|
* |
2401
|
|
|
* @return mixed |
2402
|
|
|
*/ |
2403
|
|
|
function get_schema_list() |
2404
|
|
|
{ |
2405
|
|
|
// glob returns an array sorted by filename |
2406
|
|
|
$files = glob(Config::get('install_dir') . '/sql-schema/*.sql'); |
2407
|
|
|
|
2408
|
|
|
// set the keys to the db schema version |
2409
|
|
|
$files = array_reduce($files, function ($array, $file) { |
|
|
|
|
2410
|
|
|
$array[(int)basename($file, '.sql')] = $file; |
2411
|
|
|
return $array; |
2412
|
|
|
}, []); |
2413
|
|
|
|
2414
|
|
|
ksort($files); // fix dbSchema 1000 order |
2415
|
|
|
return $files; |
2416
|
|
|
} |
2417
|
|
|
|
2418
|
|
|
/** |
2419
|
|
|
* Get the current database schema, will return 0 if there is no schema. |
2420
|
|
|
* |
2421
|
|
|
* @return int |
2422
|
|
|
*/ |
2423
|
|
|
function get_db_schema() |
2424
|
|
|
{ |
2425
|
|
|
try { |
2426
|
|
|
$db = \LibreNMS\DB\Eloquent::DB(); |
2427
|
|
|
if ($db) { |
|
|
|
|
2428
|
|
|
return (int)$db->table('dbSchema') |
2429
|
|
|
->orderBy('version', 'DESC') |
2430
|
|
|
->value('version'); |
2431
|
|
|
} |
2432
|
|
|
} catch (PDOException $e) { |
2433
|
|
|
// return default |
|
|
|
|
2434
|
|
|
} |
2435
|
|
|
|
2436
|
|
|
return 0; |
2437
|
|
|
} |
2438
|
|
|
|
2439
|
|
|
/** |
2440
|
|
|
* @param $device |
2441
|
|
|
* @return int|null |
2442
|
|
|
*/ |
2443
|
|
|
function get_device_oid_limit($device) |
2444
|
|
|
{ |
2445
|
|
|
// device takes priority |
2446
|
|
|
if ($device['snmp_max_oid'] > 0) { |
2447
|
|
|
return $device['snmp_max_oid']; |
2448
|
|
|
} |
2449
|
|
|
|
2450
|
|
|
// then os |
2451
|
|
|
$os_max = Config::getOsSetting($device['os'], 'snmp_max_oid', 0); |
2452
|
|
|
if ($os_max > 0) { |
2453
|
|
|
return $os_max; |
2454
|
|
|
} |
2455
|
|
|
|
2456
|
|
|
// then global |
2457
|
|
|
$global_max = Config::get('snmp.max_oid', 10); |
2458
|
|
|
return $global_max > 0 ? $global_max : 10; |
2459
|
|
|
} |
2460
|
|
|
|
2461
|
|
|
/** |
2462
|
|
|
* Strip out non-numeric characters |
2463
|
|
|
*/ |
2464
|
|
|
function return_num($entry) |
2465
|
|
|
{ |
2466
|
|
|
if (!is_numeric($entry)) { |
2467
|
|
|
preg_match('/-?\d*\.?\d+/', $entry, $num_response); |
2468
|
|
|
return $num_response[0]; |
2469
|
|
|
} |
2470
|
|
|
} |
2471
|
|
|
|
2472
|
|
|
/** |
2473
|
|
|
* If Distributed, create a lock, then purge the mysql table |
2474
|
|
|
* |
2475
|
|
|
* @param string $table |
2476
|
|
|
* @param string $sql |
2477
|
|
|
* @return int exit code |
2478
|
|
|
*/ |
2479
|
|
|
function lock_and_purge($table, $sql) |
2480
|
|
|
{ |
2481
|
|
|
try { |
2482
|
|
|
$purge_name = $table . '_purge'; |
2483
|
|
|
|
2484
|
|
|
if (Config::get('distributed_poller')) { |
2485
|
|
|
MemcacheLock::lock($purge_name, 0, 86000); |
2486
|
|
|
} |
2487
|
|
|
$purge_days = Config::get($purge_name); |
2488
|
|
|
|
2489
|
|
|
$name = str_replace('_', ' ', ucfirst($table)); |
2490
|
|
|
if (is_numeric($purge_days)) { |
2491
|
|
|
if (dbDelete($table, $sql, array($purge_days))) { |
2492
|
|
|
echo "$name cleared for entries over $purge_days days\n"; |
2493
|
|
|
} |
2494
|
|
|
} |
2495
|
|
|
return 0; |
2496
|
|
|
} catch (LockException $e) { |
2497
|
|
|
echo $e->getMessage() . PHP_EOL; |
2498
|
|
|
return -1; |
2499
|
|
|
} |
2500
|
|
|
} |
2501
|
|
|
|
2502
|
|
|
/** |
2503
|
|
|
* Convert space separated hex OID content to character |
2504
|
|
|
* |
2505
|
|
|
* @param string $hex_string |
2506
|
|
|
* @return string $chr_string |
2507
|
|
|
*/ |
2508
|
|
|
|
2509
|
|
|
function hexbin($hex_string) |
2510
|
|
|
{ |
2511
|
|
|
$chr_string = ''; |
2512
|
|
|
foreach (explode(' ', $hex_string) as $a) { |
2513
|
|
|
$chr_string .= chr(hexdec($a)); |
|
|
|
|
2514
|
|
|
} |
2515
|
|
|
return $chr_string; |
2516
|
|
|
} |
2517
|
|
|
|
2518
|
|
|
/** |
2519
|
|
|
* Check if disk is valid to poll. |
2520
|
|
|
* Settings: bad_disk_regexp |
2521
|
|
|
* |
2522
|
|
|
* @param array $disk |
2523
|
|
|
* @param array $device |
2524
|
|
|
* @return bool |
2525
|
|
|
*/ |
2526
|
|
|
function is_disk_valid($disk, $device) |
2527
|
|
|
{ |
2528
|
|
|
foreach (Config::getCombined($device['os'], 'bad_disk_regexp') as $bir) { |
2529
|
|
|
if (preg_match($bir ."i", $disk['diskIODevice'])) { |
2530
|
|
|
d_echo("Ignored Disk: {$disk['diskIODevice']} (matched: $bir)\n"); |
2531
|
|
|
return false; |
2532
|
|
|
} |
2533
|
|
|
} |
2534
|
|
|
return true; |
2535
|
|
|
} |
2536
|
|
|
|
2537
|
|
|
|
2538
|
|
|
/** |
2539
|
|
|
* Queues a hostname to be refreshed by Oxidized |
2540
|
|
|
* Settings: oxidized.url |
2541
|
|
|
* |
2542
|
|
|
* @param string $hostname |
2543
|
|
|
* @param string $msg |
2544
|
|
|
* @param string $username |
2545
|
|
|
* @return bool |
2546
|
|
|
*/ |
2547
|
|
|
function oxidized_node_update($hostname, $msg, $username = 'not_provided') |
2548
|
|
|
{ |
2549
|
|
|
// Work around https://github.com/rack/rack/issues/337 |
2550
|
|
|
$msg = str_replace("%", "", $msg); |
2551
|
|
|
$postdata = ["user" => $username, "msg" => $msg]; |
2552
|
|
|
$oxidized_url = Config::get('oxidized.url'); |
2553
|
|
|
if (!empty($oxidized_url)) { |
2554
|
|
|
Requests::put("$oxidized_url/node/next/$hostname", [], json_encode($postdata), ['proxy' => get_proxy()]); |
2555
|
|
|
return true; |
2556
|
|
|
} |
2557
|
|
|
return false; |
2558
|
|
|
}//end oxidized_node_update() |
2559
|
|
|
|
Let?s assume that you have a directory layout like this:
and let?s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: