Issues (2963)

includes/common.php (1 issue)

1
<?php
2
/*
3
 * LibreNMS - Common Functions
4
 *
5
 * Original Observium version by: Adam Armstrong, Tom Laermans
6
 * Copyright (c) 2009-2012 Adam Armstrong.
7
 *
8
 * Additions for LibreNMS by: Neil Lathwood, Paul Gear, Tim DuFrane
9
 * Copyright (c) 2014-2015 Neil Lathwood <http://www.lathwood.co.uk>
10
 * Copyright (c) 2014-2015 Gear Consulting Pty Ltd <http://libertysys.com.au/>
11
 *
12
 * This program is free software: you can redistribute it and/or modify it
13
 * under the terms of the GNU General Public License as published by the
14
 * Free Software Foundation, either version 3 of the License, or (at your
15
 * option) any later version.  Please see LICENSE.txt at the top level of
16
 * the source code distribution for details.
17
 */
18
19
use LibreNMS\Config;
20
use LibreNMS\Enum\Alert;
21
use LibreNMS\Exceptions\InvalidIpException;
22
use LibreNMS\Util\Debug;
23
use LibreNMS\Util\Git;
24
use LibreNMS\Util\IP;
25
use LibreNMS\Util\Laravel;
26
use Symfony\Component\Process\Process;
27
28
function generate_priority_status($priority)
29
{
30
    $map = [
31
        'emerg'     => 2,
32
        'alert'     => 2,
33
        'crit'      => 2,
34
        'err'       => 2,
35
        'warning'   => 1,
36
        'notice'    => 0,
37
        'info'      => 0,
38
        'debug'     => 3,
39
        ''          => 0,
40
    ];
41
42
    return isset($map[$priority]) ? $map[$priority] : 0;
43
}
44
45
function graylog_severity_label($severity)
46
{
47
    $map = [
48
        '0' => 'label-danger',
49
        '1' => 'label-danger',
50
        '2' => 'label-danger',
51
        '3' => 'label-danger',
52
        '4' => 'label-warning',
53
        '5' => 'label-info',
54
        '6' => 'label-info',
55
        '7' => 'label-default',
56
        ''  => 'label-info',
57
    ];
58
    $barColor = isset($map[$severity]) ? $map[$severity] : 'label-info';
59
60
    return '<span class="alert-status ' . $barColor . '" style="margin-right:8px;float:left;"></span>';
61
}
62
63
/**
64
 * Execute and snmp command, filter debug output unless -v is specified
65
 *
66
 * @param  array  $command
67
 * @return null|string
68
 */
69
function external_exec($command)
70
{
71
    $device = DeviceCache::getPrimary();
72
73
    $proc = new Process($command);
74
    $proc->setTimeout(Config::get('snmp.exec_timeout', 1200));
75
76
    if (Debug::isEnabled() && ! Debug::isVerbose()) {
77
        $patterns = [
78
            '/-c\' \'[\S]+\'/',
79
            '/-u\' \'[\S]+\'/',
80
            '/-U\' \'[\S]+\'/',
81
            '/-A\' \'[\S]+\'/',
82
            '/-X\' \'[\S]+\'/',
83
            '/-P\' \'[\S]+\'/',
84
            '/-H\' \'[\S]+\'/',
85
            '/(udp|udp6|tcp|tcp6):([^:]+):([\d]+)/',
86
        ];
87
        $replacements = [
88
            '-c\' \'COMMUNITY\'',
89
            '-u\' \'USER\'',
90
            '-U\' \'USER\'',
91
            '-A\' \'PASSWORD\'',
92
            '-X\' \'PASSWORD\'',
93
            '-P\' \'PASSWORD\'',
94
            '-H\' \'HOSTNAME\'',
95
            '\1:HOSTNAME:\3',
96
        ];
97
98
        $debug_command = preg_replace($patterns, $replacements, $proc->getCommandLine());
99
        c_echo('SNMP[%c' . $debug_command . "%n]\n");
100
    } elseif (Debug::isVerbose()) {
101
        c_echo('SNMP[%c' . $proc->getCommandLine() . "%n]\n");
102
    }
103
104
    $proc->run();
105
    $output = $proc->getOutput();
106
107
    if ($proc->getExitCode()) {
108
        if (Str::startsWith($proc->getErrorOutput(), 'Invalid authentication protocol specified')) {
109
            Log::event('Unsupported SNMP authentication algorithm - ' . $proc->getExitCode(), optional($device)->device_id, 'poller', Alert::ERROR);
110
        } elseif (Str::startsWith($proc->getErrorOutput(), 'Invalid privacy protocol specified')) {
111
            Log::event('Unsupported SNMP privacy algorithm - ' . $proc->getExitCode(), optional($device)->device_id, 'poller', Alert::ERROR);
112
        }
113
        d_echo('Exitcode: ' . $proc->getExitCode());
114
        d_echo($proc->getErrorOutput());
115
    }
116
117
    if (Debug::isEnabled() && ! Debug::isVerbose()) {
118
        $ip_regex = '/(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/';
119
        $debug_output = preg_replace($ip_regex, '*', $output);
120
        d_echo($debug_output . PHP_EOL);
121
    } elseif (Debug::isVerbose()) {
122
        d_echo($output . PHP_EOL);
123
    }
124
    d_echo($proc->getErrorOutput());
125
126
    return $output;
127
}
128
129
function shorthost($hostname, $len = 12)
130
{
131
    // IP addresses should not be shortened
132
    if (filter_var($hostname, FILTER_VALIDATE_IP)) {
133
        return $hostname;
134
    }
135
    $len = Config::get('shorthost_target_length', $len);
136
137
    $parts = explode('.', $hostname);
138
    $shorthost = $parts[0];
139
    $i = 1;
140
    while ($i < count($parts) && strlen($shorthost . '.' . $parts[$i]) < $len) {
141
        $shorthost = $shorthost . '.' . $parts[$i];
142
        $i++;
143
    }
144
145
    return $shorthost;
146
}
147
148
function print_error($text)
149
{
150
    if (Laravel::isCli()) {
151
        c_echo('%r' . $text . "%n\n");
152
    } else {
153
        echo '<div class="alert alert-danger"><i class="fa fa-fw fa-exclamation-circle" aria-hidden="true"></i> ' . $text . '</div>';
154
    }
155
}
156
157
function print_message($text)
158
{
159
    if (Laravel::isCli()) {
160
        c_echo('%g' . $text . "%n\n");
161
    } else {
162
        echo '<div class="alert alert-success"><i class="fa fa-fw fa-check-circle" aria-hidden="true"></i> ' . $text . '</div>';
163
    }
164
}
165
166
function get_sensor_rrd($device, $sensor)
167
{
168
    return Rrd::name($device['hostname'], get_sensor_rrd_name($device, $sensor));
169
}
170
171
function get_sensor_rrd_name($device, $sensor)
172
{
173
    // For IPMI, sensors tend to change order, and there is no index, so we prefer to use the description as key here.
174
    if (Config::getOsSetting($device['os'], 'sensor_descr') || $sensor['poller_type'] == 'ipmi') {
175
        return ['sensor', $sensor['sensor_class'], $sensor['sensor_type'], $sensor['sensor_descr']];
176
    } else {
177
        return ['sensor', $sensor['sensor_class'], $sensor['sensor_type'], $sensor['sensor_index']];
178
    }
179
}
180
181
function get_port_rrdfile_path($hostname, $port_id, $suffix = '')
182
{
183
    return Rrd::name($hostname, Rrd::portName($port_id, $suffix));
184
}
185
186
function get_port_by_index_cache($device_id, $ifIndex)
187
{
188
    global $port_index_cache;
189
190
    if (isset($port_index_cache[$device_id][$ifIndex]) && is_array($port_index_cache[$device_id][$ifIndex])) {
191
        $port = $port_index_cache[$device_id][$ifIndex];
192
    } else {
193
        $port = get_port_by_ifIndex($device_id, $ifIndex);
194
        $port_index_cache[$device_id][$ifIndex] = $port;
195
    }
196
197
    return $port;
198
}
199
200
function get_port_by_ifIndex($device_id, $ifIndex)
201
{
202
    return dbFetchRow('SELECT * FROM `ports` WHERE `device_id` = ? AND `ifIndex` = ?', [$device_id, $ifIndex]);
203
}
204
205
function table_from_entity_type($type)
206
{
207
    // Fuck you, english pluralisation.
208
    if ($type == 'storage') {
209
        return $type;
210
    } else {
211
        return $type . 's';
212
    }
213
}
214
215
function get_entity_by_id_cache($type, $id)
216
{
217
    global $entity_cache;
218
219
    $table = table_from_entity_type($type);
220
221
    if (is_array($entity_cache[$type][$id])) {
222
        $entity = $entity_cache[$type][$id];
223
    } else {
224
        $entity = dbFetchRow('SELECT * FROM `' . $table . '` WHERE `' . $type . '_id` = ?', [$id]);
225
        $entity_cache[$type][$id] = $entity;
226
    }
227
228
    return $entity;
229
}
230
231
function get_port_by_id($port_id)
232
{
233
    if (is_numeric($port_id)) {
234
        $port = dbFetchRow('SELECT * FROM `ports` WHERE `port_id` = ?', [$port_id]);
235
        if (is_array($port)) {
236
            return $port;
237
        } else {
238
            return false;
239
        }
240
    }
241
}
242
243
function get_application_by_id($application_id)
244
{
245
    if (is_numeric($application_id)) {
246
        $application = dbFetchRow('SELECT * FROM `applications` WHERE `app_id` = ?', [$application_id]);
247
        if (is_array($application)) {
248
            return $application;
249
        } else {
250
            return false;
251
        }
252
    }
253
}
254
255
function get_sensor_by_id($sensor_id)
256
{
257
    if (is_numeric($sensor_id)) {
258
        $sensor = dbFetchRow('SELECT * FROM `sensors` WHERE `sensor_id` = ?', [$sensor_id]);
259
        if (is_array($sensor)) {
260
            return $sensor;
261
        } else {
262
            return false;
263
        }
264
    }
265
}
266
267
function get_device_id_by_port_id($port_id)
268
{
269
    if (is_numeric($port_id)) {
270
        $device_id = dbFetchCell('SELECT `device_id` FROM `ports` WHERE `port_id` = ?', [$port_id]);
271
        if (is_numeric($device_id)) {
272
            return $device_id;
273
        } else {
274
            return false;
275
        }
276
    }
277
}
278
279
function get_device_id_by_app_id($app_id)
280
{
281
    if (is_numeric($app_id)) {
282
        $device_id = dbFetchCell('SELECT `device_id` FROM `applications` WHERE `app_id` = ?', [$app_id]);
283
        if (is_numeric($device_id)) {
284
            return $device_id;
285
        } else {
286
            return false;
287
        }
288
    }
289
}
290
291
function ifclass($ifOperStatus, $ifAdminStatus)
292
{
293
    // fake a port model
294
    return \LibreNMS\Util\Url::portLinkDisplayClass((object) ['ifOperStatus' => $ifOperStatus, 'ifAdminStatus' => $ifAdminStatus]);
295
}
296
297
function device_by_name($name)
298
{
299
    return device_by_id_cache(getidbyname($name));
300
}
301
302
function accesspoint_by_id($ap_id, $refresh = '0')
303
{
304
    $ap = dbFetchRow('SELECT * FROM `access_points` WHERE `accesspoint_id` = ?', [$ap_id]);
305
306
    return $ap;
307
}
308
309
function device_by_id_cache($device_id, $refresh = false)
310
{
311
    $model = $refresh ? DeviceCache::refresh((int) $device_id) : DeviceCache::get((int) $device_id);
312
313
    $device = $model->toArray();
314
    $device['location'] = $model->location->location ?? null;
315
    $device['lat'] = $model->location->lat ?? null;
316
    $device['lng'] = $model->location->lng ?? null;
317
    $device['attribs'] = $model->getAttribs();
318
319
    return $device;
320
}
321
322
function truncate($substring, $max = 50, $rep = '...')
323
{
324
    if (strlen($substring) < 1) {
325
        $string = $rep;
326
    } else {
327
        $string = $substring;
328
    }
329
    $leave = $max - strlen($rep);
330
    if (strlen($string) > $max) {
331
        return substr_replace($string, $rep, $leave);
332
    } else {
333
        return $string;
334
    }
335
}
336
337
function gethostbyid($device_id)
338
{
339
    return DeviceCache::get((int) $device_id)->hostname;
340
}
341
342
function strgen($length = 16)
343
{
344
    $entropy = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e',
345
        'E', 'f', 'F', 'g', 'G', 'h', 'H', 'i', 'I', 'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n',
346
        'N', 'o', 'O', 'p', 'P', 'q', 'Q', 'r', 'R', 's', 'S', 't', 'T', 'u', 'U', 'v', 'V', 'w',
347
        'W', 'x', 'X', 'y', 'Y', 'z', 'Z', ];
348
    $string = '';
349
350
    for ($i = 0; $i < $length; $i++) {
351
        $key = mt_rand(0, 61);
352
        $string .= $entropy[$key];
353
    }
354
355
    return $string;
356
}
357
358
function getpeerhost($id)
359
{
360
    return dbFetchCell('SELECT `device_id` from `bgpPeers` WHERE `bgpPeer_id` = ?', [$id]);
361
}
362
363
function getifindexbyid($id)
364
{
365
    return dbFetchCell('SELECT `ifIndex` FROM `ports` WHERE `port_id` = ?', [$id]);
366
}
367
368
function getifbyid($id)
369
{
370
    return dbFetchRow('SELECT * FROM `ports` WHERE `port_id` = ?', [$id]);
371
}
372
373
function getifdescrbyid($id)
374
{
375
    return dbFetchCell('SELECT `ifDescr` FROM `ports` WHERE `port_id` = ?', [$id]);
376
}
377
378
function getidbyname($hostname)
379
{
380
    return DeviceCache::getByHostname($hostname)->device_id;
381
}
382
383
function zeropad($num, $length = 2)
384
{
385
    return str_pad($num, $length, '0', STR_PAD_LEFT);
386
}
387
388
function set_dev_attrib($device, $attrib_type, $attrib_value)
389
{
390
    return DeviceCache::get((int) $device['device_id'])->setAttrib($attrib_type, $attrib_value);
391
}
392
393
function get_dev_attribs($device_id)
394
{
395
    return DeviceCache::get((int) $device_id)->getAttribs();
396
}
397
398
function get_dev_entity_state($device)
399
{
400
    $state = [];
401
    foreach (dbFetchRows('SELECT * FROM entPhysical_state WHERE `device_id` = ?', [$device]) as $entity) {
402
        $state['group'][$entity['group']][$entity['entPhysicalIndex']][$entity['subindex']][$entity['key']] = $entity['value'];
403
        $state['index'][$entity['entPhysicalIndex']][$entity['subindex']][$entity['group']][$entity['key']] = $entity['value'];
404
    }
405
406
    return $state;
407
}
408
409
function get_dev_attrib($device, $attrib_type)
410
{
411
    return DeviceCache::get((int) $device['device_id'])->getAttrib($attrib_type);
412
}
413
414
function del_dev_attrib($device, $attrib_type)
415
{
416
    return DeviceCache::get((int) $device['device_id'])->forgetAttrib($attrib_type);
417
}
418
419
/**
420
 * Output using console color if possible
421
 * https://github.com/pear/Console_Color2/blob/master/examples/documentation
422
 *
423
 * @param  string  $string  the string to print with console color
424
 * @param  bool  $enabled  if set to false, this function does nothing
425
 */
426
function c_echo($string, $enabled = true)
427
{
428
    if (! $enabled) {
429
        return;
430
    }
431
432
    if (Laravel::isCli()) {
433
        global $console_color;
434
        if ($console_color) {
435
            echo $console_color->convert($string);
436
        } else {
437
            // limited functionality for validate.php
438
            $search = [
439
                '/%n/',
440
                '/%g/',
441
                '/%R/',
442
                '/%Y/',
443
                '/%B/',
444
                '/%((%)|.)/', // anything left over replace with empty string
445
            ];
446
            $replace = [
447
                "\e[0m",
448
                "\e[32m",
449
                "\e[1;31m",
450
                "\e[1;33m",
451
                "\e[1;34m",
452
                '',
453
            ];
454
            echo preg_replace($search, $replace, $string);
455
        }
456
    } else {
457
        echo preg_replace('/%((%)|.)/', '', $string);
458
    }
459
}
460
461
/*
462
 * @return true if client IP address is authorized to access graphs
463
 */
464
function is_client_authorized($clientip)
465
{
466
    if (Config::get('allow_unauth_graphs', false)) {
467
        d_echo("Unauthorized graphs allowed\n");
468
469
        return true;
470
    }
471
472
    foreach (Config::get('allow_unauth_graphs_cidr', []) as $range) {
473
        try {
474
            if (IP::parse($clientip)->inNetwork($range)) {
475
                d_echo("Unauthorized graphs allowed from $range\n");
476
477
                return true;
478
            }
479
        } catch (InvalidIpException $e) {
480
            d_echo("Client IP ($clientip) is invalid.\n");
481
        }
482
    }
483
484
    return false;
485
} // is_client_authorized
486
487
/*
488
 * @return an array of all graph subtypes for the given type
489
 */
490
function get_graph_subtypes($type, $device = null)
491
{
492
    $type = basename($type);
493
    $types = [];
494
495
    // find the subtypes defined in files
496
    if ($handle = opendir(Config::get('install_dir') . "/includes/html/graphs/$type/")) {
497
        while (false !== ($file = readdir($handle))) {
498
            if ($file != '.' && $file != '..' && $file != 'auth.inc.php' && strstr($file, '.inc.php')) {
499
                $types[] = str_replace('.inc.php', '', $file);
500
            }
501
        }
502
        closedir($handle);
503
    }
504
505
    sort($types);
506
507
    return $types;
508
} // get_graph_subtypes
509
510
function get_smokeping_files($device)
511
{
512
    $smokeping = new \LibreNMS\Util\Smokeping(DeviceCache::get((int) $device['device_id']));
513
514
    return $smokeping->findFiles();
515
}
516
517
function generate_smokeping_file($device, $file = '')
518
{
519
    $smokeping = new \LibreNMS\Util\Smokeping(DeviceCache::get((int) $device['device_id']));
520
521
    return $smokeping->generateFileName($file);
522
}
523
524
/*
525
 * @return rounded value to 10th/100th/1000th depending on input (valid: 10, 100, 1000)
526
 */
527
function round_Nth($val, $round_to)
528
{
529
    if (($round_to == '10') || ($round_to == '100') || ($round_to == '1000')) {
530
        $diff = $val % $round_to;
531
        if ($diff >= ($round_to / 2)) {
532
            $ret = $val + ($round_to - $diff);
533
        } else {
534
            $ret = $val - $diff;
535
        }
536
537
        return $ret;
538
    }
539
} // end round_Nth
540
541
function is_customoid_graph($type, $subtype)
542
{
543
    if (! empty($subtype) && $type == 'customoid') {
544
        return true;
545
    }
546
547
    return false;
548
} // is_customoid_graph
549
550
//
551
// maintain a simple cache of objects
552
//
553
554
function object_add_cache($section, $obj)
555
{
556
    global $object_cache;
557
    $object_cache[$section][$obj] = true;
558
} // object_add_cache
559
560
function object_is_cached($section, $obj)
561
{
562
    global $object_cache;
563
    if (array_key_exists($obj, $object_cache)) {
564
        return $object_cache[$section][$obj];
565
    } else {
566
        return false;
567
    }
568
} // object_is_cached
569
570
/**
571
 * Checks if config allows us to ping this device
572
 * $attribs contains an array of all of this devices
573
 * attributes
574
 *
575
 * @param  array  $attribs  Device attributes
576
 * @return bool
577
 **/
578
function can_ping_device($attribs)
579
{
580
    if (Config::get('icmp_check') && ! (isset($attribs['override_icmp_disable']) && $attribs['override_icmp_disable'] == 'true')) {
581
        return true;
582
    } else {
583
        return false;
584
    }
585
} // end can_ping_device
586
587
function search_phrase_column($c)
588
{
589
    global $searchPhrase;
590
591
    return "$c LIKE '%$searchPhrase%'";
592
} // search_phrase_column
593
594
/**
595
 * Constructs the path to an RRD for the Ceph application
596
 *
597
 * @param  string  $gtype  The type of rrd we're looking for
598
 * @return string
599
 **/
600
function ceph_rrd($gtype)
601
{
602
    global $device;
603
    global $vars;
604
605
    if ($gtype == 'osd') {
606
        $var = $vars['osd'];
607
    } else {
608
        $var = $vars['pool'];
609
    }
610
611
    return Rrd::name($device['hostname'], ['app', 'ceph', $vars['id'], $gtype, $var]);
612
} // ceph_rrd
613
614
/**
615
 * Parse location field for coordinates
616
 *
617
 * @param string location The location field to look for coords in.
618
 * @return array|bool Containing the lat and lng coords
619
 **/
620
function parse_location($location)
621
{
622
    preg_match('/\[(-?[0-9. ]+), *(-?[0-9. ]+)\]/', $location, $tmp_loc);
623
    if (is_numeric($tmp_loc[1]) && is_numeric($tmp_loc[2])) {
624
        return ['lat' => $tmp_loc[1], 'lng' => $tmp_loc[2]];
625
    }
626
627
    return false;
628
}//end parse_location()
629
630
/**
631
 * Returns version info
632
 *
633
 * @param  bool  $remote  fetch remote version info from github
634
 * @return array
635
 */
636
function version_info($remote = false)
637
{
638
    $version = \LibreNMS\Util\Version::get();
639
    $output = [
640
        'local_ver' => $version->local(),
641
    ];
642
    if (Git::repoPresent() && Git::binaryExists()) {
643
        if ($remote === true && Config::get('update_channel') == 'master') {
644
            $api = curl_init();
645
            set_curl_proxy($api);
646
            curl_setopt($api, CURLOPT_USERAGENT, 'LibreNMS');
647
            curl_setopt($api, CURLOPT_URL, Config::get('github_api') . 'commits/master');
648
            curl_setopt($api, CURLOPT_RETURNTRANSFER, 1);
649
            curl_setopt($api, CURLOPT_TIMEOUT, 5);
650
            curl_setopt($api, CURLOPT_TIMEOUT_MS, 5000);
651
            curl_setopt($api, CURLOPT_CONNECTTIMEOUT, 5);
652
            $output['github'] = json_decode(curl_exec($api), true);
653
        }
654
        [$local_sha, $local_date] = explode('|', rtrim(`git show --pretty='%H|%ct' -s HEAD`));
655
        $output['local_sha'] = $local_sha;
656
        $output['local_date'] = $local_date;
657
        $output['local_branch'] = rtrim(`git rev-parse --abbrev-ref HEAD`);
658
    }
659
    $output['db_schema'] = vsprintf('%s (%s)', $version->database());
660
    $output['php_ver'] = phpversion();
661
    $output['python_ver'] = \LibreNMS\Util\Version::python();
662
    $output['mysql_ver'] = \LibreNMS\DB\Eloquent::isConnected() ? \LibreNMS\DB\Eloquent::version() : '?';
663
    $output['rrdtool_ver'] = str_replace('1.7.01.7.0', '1.7.0', implode(' ', array_slice(explode(' ', shell_exec(
664
        Config::get('rrdtool', 'rrdtool') . ' --version |head -n1'
665
    )), 1, 1)));
666
    $output['netsnmp_ver'] = str_replace('version: ', '', rtrim(shell_exec(
667
        Config::get('snmpget', 'snmpget') . ' -V 2>&1'
668
    )));
669
670
    return $output;
671
}//end version_info()
672
673
/**
674
 * Checks SNMPv3 capabilities
675
 *
676
 * SHA2 for Auth Algorithms (SHA-224,SHA-256,SHA-384,SHA-512)
677
 * AES-192, AES-256 for Privacy Algorithms
678
 */
679
function snmpv3_capabilities(): array
680
{
681
    $process = new Process([Config::get('snmpget', 'snmpget'), '--help']);
682
    $process->run();
683
684
    $ret['sha2'] = Str::contains($process->getErrorOutput(), 'SHA-512');
685
    $ret['aes256'] = Str::contains($process->getErrorOutput(), 'AES-256');
686
687
    return $ret;
688
}
689
690
/**
691
 * Convert a MySQL binary v4 (4-byte) or v6 (16-byte) IP address to a printable string.
692
 *
693
 * @param  string  $ip  A binary string containing an IP address, as returned from MySQL's INET6_ATON function
694
 * @return string Empty if not valid.
695
 */
696
// Fuction is from https://php.net/manual/en/function.inet-ntop.php
697
function inet6_ntop($ip)
698
{
699
    $l = strlen($ip);
700
    if ($l == 4 or $l == 16) {
701
        return inet_ntop(pack('A' . $l, $ip));
702
    }
703
704
    return '';
705
}
706
707
/**
708
 * If hostname is an ip, use return sysName
709
 *
710
 * @param  array  $device  (uses hostname and sysName fields)
711
 * @param  string  $hostname
712
 * @return string
713
 */
714
function format_hostname($device, $hostname = null)
715
{
716
    if (empty($hostname)) {
717
        $hostname = $device['hostname'];
718
    }
719
720
    if (Config::get('force_hostname_to_sysname') && ! empty($device['sysName'])) {
721
        if (\LibreNMS\Util\Validate::hostname($hostname) && ! IP::isValid($hostname)) {
722
            return $device['sysName'];
723
        }
724
    }
725
726
    if (Config::get('force_ip_to_sysname') && ! empty($device['sysName'])) {
727
        if (IP::isValid($hostname)) {
728
            return $device['sysName'];
729
        }
730
    }
731
732
    return $hostname;
733
}
734
735
/**
736
 * Return valid port association modes
737
 *
738
 * @return array
739
 */
740
function get_port_assoc_modes()
741
{
742
    return [
743
        1 => 'ifIndex',
744
        2 => 'ifName',
745
        3 => 'ifDescr',
746
        4 => 'ifAlias',
747
    ];
748
}
749
750
/**
751
 * Get DB id of given port association mode name
752
 *
753
 * @param  string  $port_assoc_mode
754
 * @return int
755
 */
756
function get_port_assoc_mode_id($port_assoc_mode)
757
{
758
    $modes = array_flip(get_port_assoc_modes());
759
760
    return isset($modes[$port_assoc_mode]) ? $modes[$port_assoc_mode] : false;
761
}
762
763
/**
764
 * Get name of given port association_mode ID
765
 *
766
 * @param  int  $port_assoc_mode_id  Port association mode ID
767
 * @return bool
768
 */
769
function get_port_assoc_mode_name($port_assoc_mode_id)
770
{
771
    $modes = get_port_assoc_modes();
772
773
    return isset($modes[$port_assoc_mode_id]) ? $modes[$port_assoc_mode_id] : false;
774
}
775
776
/**
777
 * Query all ports of the given device (by ID) and build port array and
778
 * port association maps for ifIndex, ifName, ifDescr. Query port stats
779
 * if told to do so, too.
780
 *
781
 * @param  int  $device_id  ID of device to query ports for
782
 * @param  bool  $with_statistics  Query port statistics, too. (optional, default false)
783
 * @return array
784
 */
785
function get_ports_mapped($device_id, $with_statistics = false)
786
{
787
    $ports = [];
788
    $maps = [
789
        'ifIndex' => [],
790
        'ifName'  => [],
791
        'ifDescr' => [],
792
    ];
793
794
    if ($with_statistics) {
795
        /* ... including any related ports_statistics if requested */
796
        $query = 'SELECT *, `ports_statistics`.`port_id` AS `ports_statistics_port_id`, `ports`.`port_id` AS `port_id` FROM `ports` LEFT OUTER JOIN `ports_statistics` ON `ports`.`port_id` = `ports_statistics`.`port_id` WHERE `ports`.`device_id` = ? ORDER BY ports.port_id';
797
    } else {
798
        /* Query all information available for ports for this device ... */
799
        $query = 'SELECT * FROM `ports` WHERE `device_id` = ? ORDER BY port_id';
800
    }
801
802
    // Query known ports in order of discovery to make sure the latest
803
    // discoverd/polled port is in the mapping tables.
804
    foreach (dbFetchRows($query, [$device_id]) as $port) {
805
        // Store port information by ports port_id from DB
806
        $ports[$port['port_id']] = $port;
807
808
        // Build maps from ifIndex, ifName, ifDescr to port_id
809
        $maps['ifIndex'][$port['ifIndex']] = $port['port_id'];
810
        $maps['ifName'][$port['ifName']] = $port['port_id'];
811
        $maps['ifDescr'][$port['ifDescr']] = $port['port_id'];
812
    }
813
814
    return [
815
        'ports' => $ports,
816
        'maps'  => $maps,
817
    ];
818
}
819
820
/**
821
 * Calculate port_id of given port using given devices port information and port association mode
822
 *
823
 * @param  array  $ports_mapped  Port information of device queried by get_ports_mapped()
824
 * @param  array  $port  Port information as fetched from DB
825
 * @param  string  $port_association_mode  Port association mode to use for mapping
826
 * @return int port_id (or Null)
827
 */
828
function get_port_id($ports_mapped, $port, $port_association_mode)
829
{
830
    // Get port_id according to port_association_mode used for this device
831
    $port_id = null;
832
833
    /*
834
     * Information an all ports is available through $ports_mapped['ports']
835
     * This might come in handy sometime in the future to add you nifty new
836
     * port mapping schema:
837
     *
838
     * $ports = $ports_mapped['ports'];
839
    */
840
    $maps = $ports_mapped['maps'];
841
842
    if (in_array($port_association_mode, ['ifIndex', 'ifName', 'ifDescr', 'ifAlias'])) {
843
        $port_id = $maps[$port_association_mode][$port[$port_association_mode]];
844
    }
845
846
    return $port_id;
847
}
848
849
/**
850
 * Create a glue-chain
851
 *
852
 * @param  array  $tables  Initial Tables to construct glue-chain
853
 * @param  string  $target  Glue to find (usual device_id)
854
 * @param  int  $x  Recursion Anchor
855
 * @param  array  $hist  History of processed tables
856
 * @param  array  $last  Glues on the fringe
857
 * @return array|false
858
 */
859
function ResolveGlues($tables, $target, $x = 0, $hist = [], $last = [])
860
{
861
    if (sizeof($tables) == 1 && $x != 0) {
862
        if (dbFetchCell('SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME = ?', [$tables[0], $target]) == 1) {
863
            return array_merge($last, [$tables[0] . '.' . $target]);
864
        } else {
865
            return false;
866
        }
867
    } else {
868
        $x++;
869
        if ($x > 30) {
870
            //Too much recursion. Abort.
871
            return false;
872
        }
873
        foreach ($tables as $table) {
874
            if ($table == 'state_translations' && ($target == 'device_id' || $target == 'sensor_id')) {
875
                // workaround for state_translations
876
                return array_merge($last, [
877
                    'state_translations.state_index_id',
878
                    'sensors_to_state_indexes.sensor_id',
879
                    "sensors.$target",
880
                ]);
881
            } elseif ($table == 'application_metrics' && $target == 'device_id') {
882
                return array_merge($last, [
883
                    'application_metrics.app_id',
884
                    "applications.$target",
885
                ]);
886
            } elseif ($table == 'locations' && $target == 'device_id') {
887
                return array_merge($last, [
888
                    'locations.id',
889
                    'devices.device_id.location_id',
890
                ]);
891
            }
892
893
            $glues = dbFetchRows('SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME LIKE "%\_id"', [$table]);
894
            if (sizeof($glues) == 1 && $glues[0]['COLUMN_NAME'] != $target) {
895
                //Search for new candidates to expand
896
                $ntables = [];
897
                [$tmp] = explode('_', $glues[0]['COLUMN_NAME'], 2);
898
                $ntables[] = $tmp;
899
                $ntables[] = $tmp . 's';
900
                $tmp = dbFetchRows('SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME LIKE "' . substr($table, 0, -1) . '_%" && TABLE_NAME != "' . $table . '"');
901
                foreach ($tmp as $expand) {
902
                    $ntables[] = $expand['TABLE_NAME'];
903
                }
904
                $tmp = ResolveGlues($ntables, $target, $x++, array_merge($tables, $ntables), array_merge($last, [$table . '.' . $glues[0]['COLUMN_NAME']]));
905
                if (is_array($tmp)) {
906
                    return $tmp;
907
                }
908
            } else {
909
                foreach ($glues as $glue) {
910
                    if ($glue['COLUMN_NAME'] == $target) {
911
                        return array_merge($last, [$table . '.' . $target]);
912
                    } else {
913
                        [$tmp] = explode('_', $glue['COLUMN_NAME']);
914
                        $tmp .= 's';
915
                        if (! in_array($tmp, $tables) && ! in_array($tmp, $hist)) {
916
                            //Expand table
917
                            $tmp = ResolveGlues([$tmp], $target, $x++, array_merge($tables, [$tmp]), array_merge($last, [$table . '.' . $glue['COLUMN_NAME']]));
918
                            if (is_array($tmp)) {
919
                                return $tmp;
920
                            }
921
                        }
922
                    }
923
                }
924
            }
925
        }
926
    }
927
    //You should never get here.
928
    return false;
929
}
930
931
/**
932
 * Determine if a given string contains a given substring.
933
 *
934
 * @param  string  $haystack
935
 * @param  string|array  $needles
936
 * @return bool
937
 */
938
function str_i_contains($haystack, $needles)
939
{
940
    foreach ((array) $needles as $needle) {
941
        if ($needle != '' && stripos($haystack, $needle) !== false) {
942
            return true;
943
        }
944
    }
945
946
    return false;
947
}
948
949
/**
950
 * Get alert_rules sql filter by minimal severity
951
 *
952
 * @param  string|int  $min_severity
953
 * @param  string  $alert_rules_name
954
 * @return string
955
 */
956
function get_sql_filter_min_severity($min_severity, $alert_rules_name)
957
{
958
    $alert_severities = [
959
        // alert_rules.status is enum('ok','warning','critical')
960
        'ok' => 1,
961
        'warning' => 2,
962
        'critical' => 3,
963
        'ok only' => 4,
964
        'warning only' => 5,
965
        'critical only' => 6,
966
    ];
967
    if (is_numeric($min_severity)) {
968
        $min_severity_id = $min_severity;
969
    } elseif (! empty($min_severity)) {
970
        $min_severity_id = $alert_severities[$min_severity];
971
    }
972
    if (isset($min_severity_id)) {
973
        return " AND `$alert_rules_name`.`severity` " . ($min_severity_id > 3 ? '' : '>') . '= ' . ($min_severity_id > 3 ? $min_severity_id - 3 : $min_severity_id);
974
    }
975
976
    return '';
977
}
978
979
/**
980
 * Load the os definition for the device and set type and os_group
981
 * $device['os'] must be set
982
 *
983
 * @param  array  $device
984
 */
985
function load_os(&$device)
986
{
987
    if (! isset($device['os'])) {
988
        d_echo("No OS to load\n");
989
990
        return;
991
    }
992
993
    \LibreNMS\Util\OS::loadDefinition($device['os']);
994
995
    // Set type to a predefined type for the OS if it's not already set
996
    $loaded_os_type = Config::get("os.{$device['os']}.type");
997
    if ((! isset($device['attribs']['override_device_type']) && $device['attribs']['override_device_type'] != 1) && array_key_exists('type', $device) && $loaded_os_type != $device['type']) {
998
        log_event('Device type changed ' . $device['type'] . ' => ' . $loaded_os_type, $device, 'system', 3);
999
        $device['type'] = $loaded_os_type;
1000
        dbUpdate(['type' => $loaded_os_type], 'devices', 'device_id=?', [$device['device_id']]);
1001
        d_echo("Device type changed to $loaded_os_type!\n");
1002
    }
1003
1004
    if ($os_group = Config::get("os.{$device['os']}.group")) {
1005
        $device['os_group'] = $os_group;
1006
    } else {
1007
        unset($device['os_group']);
1008
    }
1009
}
1010
1011
/**
1012
 * Converts fahrenheit to celsius (with 2 decimal places)
1013
 * if $scale is not fahrenheit, it assumes celsius and  returns the value
1014
 *
1015
 * @param  float  $value
1016
 * @param  string  $scale  fahrenheit or celsius
1017
 * @return string (containing a float)
1018
 */
1019
function fahrenheit_to_celsius($value, $scale = 'fahrenheit')
1020
{
1021
    if ($scale === 'fahrenheit') {
1022
        $value = ($value - 32) / 1.8;
1023
    }
1024
1025
    return sprintf('%.02f', $value);
1026
}
1027
1028
/**
1029
 * Converts celsius to fahrenheit (with 2 decimal places)
1030
 * if $scale is not celsius, it assumes celsius and  returns the value
1031
 *
1032
 * @param  float  $value
1033
 * @param  string  $scale  fahrenheit or celsius
1034
 * @return string (containing a float)
1035
 */
1036
function celsius_to_fahrenheit($value, $scale = 'celsius')
1037
{
1038
    if ($scale === 'celsius') {
1039
        $value = ($value * 1.8) + 32;
1040
    }
1041
1042
    return sprintf('%.02f', $value);
1043
}
1044
1045
/**
1046
 * Converts string to float
1047
 */
1048
function string_to_float($value)
1049
{
1050
    return sprintf('%.02f', $value);
1051
}
1052
1053
/**
1054
 * Converts uW to dBm
1055
 * $value must be positive
1056
 */
1057
function uw_to_dbm($value)
1058
{
1059
    return 10 * log10($value / 1000);
1060
}
1061
1062
/**
1063
 * Converts mW to dBm
1064
 * $value must be positive
1065
 */
1066
function mw_to_dbm($value)
1067
{
1068
    return 10 * log10($value);
1069
}
1070
1071
/**
1072
 * @param $value
1073
 * @param  null  $default
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
1074
 * @param  int  $min
1075
 * @return null
1076
 */
1077
function set_null($value, $default = null, $min = null)
1078
{
1079
    if (! is_numeric($value)) {
1080
        return $default;
1081
    } elseif (is_nan($value)) {
1082
        return $default;
1083
    } elseif (is_infinite($value)) {
1084
        return $default;
1085
    } elseif (isset($min) && $value < $min) {
1086
        return $default;
1087
    }
1088
1089
    return $value;
1090
}
1091
/*
1092
 * @param $value
1093
 * @param int $default
1094
 * @return int
1095
 */
1096
function set_numeric($value, $default = 0)
1097
{
1098
    if (! is_numeric($value) ||
1099
        is_nan($value) ||
1100
        is_infinite($value)
1101
    ) {
1102
        $value = $default;
1103
    }
1104
1105
    return $value;
1106
}
1107
1108
function get_vm_parent_id($device)
1109
{
1110
    if (empty($device['hostname'])) {
1111
        return false;
1112
    }
1113
1114
    return dbFetchCell('SELECT `device_id` FROM `vminfo` WHERE `vmwVmDisplayName` = ? OR `vmwVmDisplayName` = ?', [$device['hostname'], $device['hostname'] . '.' . Config::get('mydomain')]);
1115
}
1116
1117
/**
1118
 * Generate a class name from a lowercase string containing - or _
1119
 * Remove - and _ and camel case words
1120
 *
1121
 * @param  string  $name  The string to convert to a class name
1122
 * @param  string  $namespace  namespace to prepend to the name for example: LibreNMS\
1123
 * @return string Class name
1124
 */
1125
function str_to_class($name, $namespace = null)
1126
{
1127
    $pre_format = str_replace(['-', '_'], ' ', $name);
1128
    $class = str_replace(' ', '', ucwords(strtolower($pre_format)));
1129
    $class = preg_replace_callback('/^(\d)(.)/', function ($matches) {
1130
        $numbers = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine'];
1131
1132
        return $numbers[$matches[1]] . strtoupper($matches[2]);
1133
    }, $class);
1134
1135
    return $namespace . $class;
1136
}
1137
1138
/**
1139
 * Index an array by a column
1140
 *
1141
 * @param  array  $array
1142
 * @param  string|int  $column
1143
 * @return array
1144
 */
1145
function array_by_column($array, $column)
1146
{
1147
    return array_combine(array_column($array, $column), $array);
1148
}
1149