2009-09-07 11:07:59 +00:00
<? php
2007-04-03 14:10:23 +00:00
2012-05-21 16:52:02 +00:00
/**
2016-09-08 14:12:23 +01:00
* LibreNMS
2012-05-21 16:52:02 +00:00
*
2016-09-08 14:12:23 +01:00
* This file is part of LibreNMS.
2012-05-21 16:52:02 +00:00
*
* @copyright (C) 2006 - 2012 Adam Armstrong
*/
2020-01-30 13:20:30 +01:00
use App\Models\Device ;
2020-04-17 17:37:56 -05:00
use Illuminate\Support\Str ;
2017-09-21 12:02:01 -05:00
use LibreNMS\Config ;
2016-08-21 08:07:14 -05:00
use LibreNMS\Exceptions\HostExistsException ;
use LibreNMS\Exceptions\HostIpExistsException ;
use LibreNMS\Exceptions\HostUnreachableException ;
use LibreNMS\Exceptions\HostUnreachablePingException ;
use LibreNMS\Exceptions\InvalidPortAssocModeException ;
use LibreNMS\Exceptions\SnmpVersionUnsupportedException ;
2020-05-19 22:08:41 -05:00
use LibreNMS\Fping ;
2020-10-05 07:26:37 -05:00
use LibreNMS\Modules\Core ;
2019-01-19 12:46:03 -06:00
use LibreNMS\Util\IPv4 ;
use LibreNMS\Util\IPv6 ;
2019-03-18 03:09:58 +01:00
use LibreNMS\Util\Time ;
2019-08-05 14:16:05 -05:00
use PHPMailer\PHPMailer\PHPMailer ;
use Symfony\Component\Process\Process ;
2016-08-21 08:07:14 -05:00
2020-09-21 15:40:17 +02:00
if ( ! function_exists ( 'set_debug' )) {
2019-01-09 19:36:32 -06:00
/**
* Set debugging output
*
* @param bool $state If debug is enabled or not
* @param bool $silence When not debugging, silence every php error
* @return bool
*/
function set_debug ( $state = true , $silence = false )
{
global $debug ;
2018-07-13 17:08:00 -05:00
2019-01-09 19:36:32 -06:00
$debug = $state ; // set to global
2018-07-13 17:08:00 -05:00
2019-01-09 19:36:32 -06:00
restore_error_handler (); // disable Laravel error handler
2018-07-13 17:08:00 -05:00
2019-01-09 19:36:32 -06:00
if ( isset ( $debug ) && $debug ) {
ini_set ( 'display_errors' , 1 );
ini_set ( 'display_startup_errors' , 1 );
ini_set ( 'log_errors' , 0 );
error_reporting ( E_ALL & ~ E_NOTICE );
2018-07-13 17:08:00 -05:00
2019-01-09 19:36:32 -06:00
\LibreNMS\Util\Laravel :: enableCliDebugOutput ();
\LibreNMS\Util\Laravel :: enableQueryDebug ();
} else {
ini_set ( 'display_errors' , 0 );
ini_set ( 'display_startup_errors' , 0 );
ini_set ( 'log_errors' , 1 );
error_reporting ( $silence ? 0 : E_ERROR );
2018-07-13 17:08:00 -05:00
2019-01-09 19:36:32 -06:00
\LibreNMS\Util\Laravel :: disableCliDebugOutput ();
\LibreNMS\Util\Laravel :: disableQueryDebug ();
}
2018-07-13 17:08:00 -05:00
2019-01-09 19:36:32 -06:00
return $debug ;
}
2016-09-07 18:42:49 +01:00
} //end set_debug()
2018-04-01 15:27:41 -05:00
function array_sort_by_column ( $array , $on , $order = SORT_ASC )
2016-08-28 12:32:58 -05:00
{
2020-09-21 15:40:17 +02:00
$new_array = [];
$sortable_array = [];
2012-05-25 10:34:01 +00:00
if ( count ( $array ) > 0 ) {
foreach ( $array as $k => $v ) {
if ( is_array ( $v )) {
foreach ( $v as $k2 => $v2 ) {
if ( $k2 == $on ) {
$sortable_array [ $k ] = $v2 ;
}
}
2016-08-28 12:32:58 -05:00
} else {
2012-05-25 10:34:01 +00:00
$sortable_array [ $k ] = $v ;
}
}
switch ( $order ) {
2016-08-28 12:32:58 -05:00
case SORT_ASC :
asort ( $sortable_array );
break ;
case SORT_DESC :
arsort ( $sortable_array );
break ;
2012-05-25 10:34:01 +00:00
}
foreach ( $sortable_array as $k => $v ) {
$new_array [ $k ] = $array [ $k ];
}
}
2020-09-21 15:40:17 +02:00
2012-05-25 10:34:01 +00:00
return $new_array ;
2012-04-08 16:21:52 +00:00
}
2016-08-28 12:32:58 -05:00
function only_alphanumeric ( $string )
{
2015-07-13 20:10:26 +02:00
return preg_replace ( '/[^a-zA-Z0-9]/' , '' , $string );
2009-04-23 21:13:56 +00:00
}
2018-07-12 15:30:39 -05:00
/**
* Parse cli discovery or poller modules and set config for this run
*
* @param string $type discovery or poller
* @param array $options get_opts array (only m key is checked)
* @return bool
*/
function parse_modules ( $type , $options )
{
$override = false ;
if ( $options [ 'm' ]) {
Config :: set ( " { $type } _modules" , []);
foreach ( explode ( ',' , $options [ 'm' ]) as $module ) {
// parse submodules (only supported by some modules)
2020-04-17 17:37:56 -05:00
if ( Str :: contains ( $module , '/' )) {
2020-05-19 14:35:32 -05:00
[ $module , $submodule ] = explode ( '/' , $module , 2 );
2018-07-12 15:30:39 -05:00
$existing_submodules = Config :: get ( " { $type } _submodules. $module " , []);
$existing_submodules [] = $submodule ;
Config :: set ( " { $type } _submodules. $module " , $existing_submodules );
}
$dir = $type == 'poller' ? 'polling' : $type ;
if ( is_file ( "includes/ $dir / $module .inc.php" )) {
Config :: set ( " { $type } _modules. $module " , 1 );
$override = true ;
}
}
// display selected modules
$modules = array_map ( function ( $module ) use ( $type ) {
$submodules = Config :: get ( " { $type } _submodules. $module " );
2020-09-21 15:40:17 +02:00
2018-07-12 15:30:39 -05:00
return $module . ( $submodules ? '(' . implode ( ',' , $submodules ) . ')' : '' );
}, array_keys ( Config :: get ( " { $type } _modules" , [])));
d_echo ( "Override $type modules: " . implode ( ', ' , $modules ) . PHP_EOL );
}
return $override ;
}
2016-08-28 12:32:58 -05:00
function logfile ( $string )
{
2019-06-23 00:29:12 -05:00
$fd = fopen ( Config :: get ( 'log_file' ), 'a' );
2016-08-28 12:32:58 -05:00
fputs ( $fd , $string . " \n " );
2015-07-13 20:10:26 +02:00
fclose ( $fd );
2010-11-24 11:32:53 +00:00
}
2017-01-12 11:01:34 -06:00
/**
* Check an array of regexes against a subject if any match, return true
*
* @param string $subject the string to match against
* @param array|string $regexes an array of regexes or single regex to check
* @return bool if any of the regexes matched, return true
*/
2017-01-03 20:04:18 +00:00
function preg_match_any ( $subject , $regexes )
{
2020-09-21 15:40:17 +02:00
foreach (( array ) $regexes as $regex ) {
2017-01-03 20:04:18 +00:00
if ( preg_match ( $regex , $subject )) {
return true ;
}
}
2020-09-21 15:40:17 +02:00
2017-01-03 20:04:18 +00:00
return false ;
2008-03-12 11:15:58 +00:00
}
2017-11-02 07:41:18 -05:00
/**
* Perform comparison of two items based on give comparison method
* Valid comparisons: =, !=, ==, !==, >=, <=, >, <, contains, starts, ends, regex
* contains, starts, ends: $a haystack, $b needle(s)
* regex: $a subject, $b regex
*
* @param mixed $a
* @param mixed $b
* @param string $comparison =, !=, ==, !== >=, <=, >, <, contains, starts, ends, regex
* @return bool
*/
function compare_var ( $a , $b , $comparison = '=' )
{
2021-03-12 18:10:14 -06:00
// handle PHP8 change to implicit casting
if ( is_numeric ( $a ) || is_numeric ( $b )) {
$a = cast_number ( $a );
$b = is_array ( $b ) ? $b : cast_number ( $b );
}
2017-11-02 07:41:18 -05:00
switch ( $comparison ) {
2020-09-21 15:59:34 +02:00
case '=' :
2017-11-02 07:41:18 -05:00
return $a == $b ;
2020-09-21 15:59:34 +02:00
case '!=' :
2017-11-02 07:41:18 -05:00
return $a != $b ;
2020-09-21 15:59:34 +02:00
case '==' :
2017-11-02 07:41:18 -05:00
return $a === $b ;
2020-09-21 15:59:34 +02:00
case '!==' :
2017-11-02 07:41:18 -05:00
return $a !== $b ;
2020-09-21 15:59:34 +02:00
case '>=' :
2017-11-02 07:41:18 -05:00
return $a >= $b ;
2020-09-21 15:59:34 +02:00
case '<=' :
2017-11-02 07:41:18 -05:00
return $a <= $b ;
2020-09-21 15:59:34 +02:00
case '>' :
2017-11-02 07:41:18 -05:00
return $a > $b ;
2020-09-21 15:59:34 +02:00
case '<' :
2017-11-02 07:41:18 -05:00
return $a < $b ;
2020-09-21 15:59:34 +02:00
case 'contains' :
2020-04-17 17:37:56 -05:00
return Str :: contains ( $a , $b );
2020-09-21 15:59:34 +02:00
case 'not_contains' :
2020-09-21 15:40:17 +02:00
return ! Str :: contains ( $a , $b );
2020-09-21 15:59:34 +02:00
case 'starts' :
2020-04-17 17:37:56 -05:00
return Str :: startsWith ( $a , $b );
2020-09-21 15:59:34 +02:00
case 'not_starts' :
2020-09-21 15:40:17 +02:00
return ! Str :: startsWith ( $a , $b );
2020-09-21 15:59:34 +02:00
case 'ends' :
2020-04-17 17:37:56 -05:00
return Str :: endsWith ( $a , $b );
2020-09-21 15:59:34 +02:00
case 'not_ends' :
2020-09-21 15:40:17 +02:00
return ! Str :: endsWith ( $a , $b );
2020-09-21 15:59:34 +02:00
case 'regex' :
2020-09-21 15:40:17 +02:00
return ( bool ) preg_match ( $b , $a );
2020-09-21 15:59:34 +02:00
case 'not regex' :
2020-09-21 15:40:17 +02:00
return ! (( bool ) preg_match ( $b , $a ));
2020-09-21 15:59:34 +02:00
case 'in_array' :
2019-07-09 16:47:02 +02:00
return in_array ( $a , $b );
2020-09-21 15:59:34 +02:00
case 'not_in_array' :
2020-09-21 15:40:17 +02:00
return ! in_array ( $a , $b );
2020-09-21 15:59:34 +02:00
case 'exists' :
2020-06-14 16:21:22 +02:00
return isset ( $a ) == $b ;
2017-11-02 07:41:18 -05:00
default :
return false ;
}
}
2016-08-28 12:32:58 -05:00
function percent_colour ( $perc )
{
2015-07-13 20:10:26 +02:00
$r = min ( 255 , 5 * ( $perc - 25 ));
$b = max ( 0 , 255 - ( 5 * ( $perc + 25 )));
2011-03-27 10:21:19 +00:00
2015-07-13 20:10:26 +02:00
return sprintf ( '#%02x%02x%02x' , $r , $b , $b );
2011-03-08 17:12:43 +00:00
}
2007-06-24 14:56:47 +00:00
2017-01-24 16:16:01 -06:00
/**
* @param $device
* @return string the logo image path for this device. Images are often wide, not square.
*/
function getLogo ( $device )
{
$img = getImageName ( $device , true , 'images/logos/' );
2020-09-21 15:40:17 +02:00
if ( ! Str :: startsWith ( $img , 'generic' )) {
2017-01-24 16:16:01 -06:00
return 'images/logos/' . $img ;
}
return getIcon ( $device );
}
/**
2017-02-24 03:59:30 -06:00
* @param array $device
* @param string $class to apply to the image tag
2017-01-24 16:16:01 -06:00
* @return string an image tag with the logo for this device. Images are often wide, not square.
*/
2017-02-24 03:59:30 -06:00
function getLogoTag ( $device , $class = null )
2016-08-28 12:32:58 -05:00
{
2019-05-21 08:50:45 -05:00
$tag = '<img src="' . url ( getLogo ( $device )) . '" title="' . getImageTitle ( $device ) . '"' ;
2017-02-24 03:59:30 -06:00
if ( isset ( $class )) {
$tag .= " class= \" $class \" " ;
}
$tag .= ' />' ;
2020-09-21 15:40:17 +02:00
2017-02-24 03:59:30 -06:00
return $tag ;
2014-01-13 10:05:19 +00:00
}
2017-01-24 16:16:01 -06:00
/**
* @param $device
* @return string the path to the icon image for this device. Close to square.
*/
function getIcon ( $device )
2016-08-28 12:32:58 -05:00
{
2017-01-08 20:32:17 +01:00
return 'images/os/' . getImageName ( $device );
2016-02-23 23:44:51 -06:00
}
2017-01-24 16:16:01 -06:00
/**
* @param $device
* @return string an image tag with the icon for this device. Close to square.
*/
function getIconTag ( $device )
{
return '<img src="' . getIcon ( $device ) . '" title="' . getImageTitle ( $device ) . '"/>' ;
}
2017-01-24 23:36:33 +00:00
function getImageTitle ( $device )
{
2020-09-21 15:40:17 +02:00
return $device [ 'icon' ] ? str_replace ([ '.svg' , '.png' ], '' , $device [ 'icon' ]) : $device [ 'os' ];
2017-01-24 16:16:01 -06:00
}
function getImageName ( $device , $use_database = true , $dir = 'images/os/' )
2016-08-28 12:32:58 -05:00
{
2019-03-01 23:06:01 -06:00
return \LibreNMS\Util\Url :: findOsImage ( $device [ 'os' ], $device [ 'features' ], $use_database ? $device [ 'icon' ] : null , $dir );
2008-11-19 12:12:54 +00:00
}
2016-08-28 12:32:58 -05:00
function renamehost ( $id , $new , $source = 'console' )
{
2018-08-30 14:38:29 -05:00
$host = gethostbyid ( $id );
2015-07-13 20:10:26 +02:00
2021-03-28 17:25:30 -05:00
if ( ! is_dir ( Rrd :: dirFromHost ( $new )) && rename ( Rrd :: dirFromHost ( $host ), Rrd :: dirFromHost ( $new )) === true ) {
2018-08-30 14:38:29 -05:00
dbUpdate ([ 'hostname' => $new , 'ip' => null ], 'devices' , 'device_id=?' , [ $id ]);
2017-02-13 00:41:05 +02:00
log_event ( "Hostname changed -> $new ( $source )" , $id , 'system' , 3 );
2020-09-21 15:40:17 +02:00
2018-08-30 14:38:29 -05:00
return '' ;
2015-07-13 20:10:26 +02:00
}
2018-08-30 14:38:29 -05:00
log_event ( "Renaming of $host failed" , $id , 'system' , 5 );
2020-09-21 15:40:17 +02:00
2018-08-30 14:38:29 -05:00
return "Renaming of $host failed \n " ;
2007-04-03 14:10:23 +00:00
}
2019-12-08 22:17:27 +01:00
function device_discovery_trigger ( $id )
{
if ( isCli () === false ) {
ignore_user_abort ( true );
set_time_limit ( 0 );
}
2020-09-21 15:40:17 +02:00
$update = dbUpdate ([ 'last_discovered' => [ 'NULL' ]], 'devices' , '`device_id` = ?' , [ $id ]);
if ( ! empty ( $update ) || $update == '0' ) {
2019-12-08 22:17:27 +01:00
$message = 'Device will be rediscovered' ;
} else {
$message = 'Error rediscovering device' ;
}
2020-09-21 15:40:17 +02:00
return [ 'status' => $update , 'message' => $message ];
2019-12-08 22:17:27 +01:00
}
2016-08-28 12:32:58 -05:00
function delete_device ( $id )
{
2019-06-23 00:29:12 -05:00
global $debug ;
2017-02-01 20:17:35 +00:00
if ( isCli () === false ) {
ignore_user_abort ( true );
set_time_limit ( 0 );
}
2015-07-13 20:10:26 +02:00
$ret = '' ;
2020-09-21 15:59:34 +02:00
$host = dbFetchCell ( 'SELECT hostname FROM devices WHERE device_id = ?' , [ $id ]);
2016-08-28 12:32:58 -05:00
if ( empty ( $host )) {
2020-09-21 15:59:34 +02:00
return 'No such host.' ;
2015-07-13 20:10:26 +02:00
}
// Remove IPv4/IPv6 addresses before removing ports as they depend on port_id
2020-09-21 15:59:34 +02:00
dbQuery ( 'DELETE `ipv4_addresses` FROM `ipv4_addresses` INNER JOIN `ports` ON `ports`.`port_id`=`ipv4_addresses`.`port_id` WHERE `device_id`=?' , [ $id ]);
dbQuery ( 'DELETE `ipv6_addresses` FROM `ipv6_addresses` INNER JOIN `ports` ON `ports`.`port_id`=`ipv6_addresses`.`port_id` WHERE `device_id`=?' , [ $id ]);
2015-07-13 20:10:26 +02:00
2020-08-17 10:34:35 +02:00
//Remove Outages
\App\Models\Availability :: where ( 'device_id' , $id ) -> delete ();
\App\Models\DeviceOutage :: where ( 'device_id' , $id ) -> delete ();
2020-04-16 09:19:58 -05:00
\App\Models\Port :: where ( 'device_id' , $id )
-> with ( 'device' )
-> select ([ 'port_id' , 'device_id' , 'ifIndex' , 'ifName' , 'ifAlias' , 'ifDescr' ])
-> chunk ( 100 , function ( $ports ) use ( & $ret ) {
foreach ( $ports as $port ) {
$port -> delete ();
$ret .= "Removed interface $port->port_id (" . $port -> getLabel () . ") \n " ;
}
});
2015-07-13 20:10:26 +02:00
2017-02-23 23:03:07 +00:00
// Remove sensors manually due to constraints
2020-09-21 15:59:34 +02:00
foreach ( dbFetchRows ( 'SELECT * FROM `sensors` WHERE `device_id` = ?' , [ $id ]) as $sensor ) {
2017-02-23 23:03:07 +00:00
$sensor_id = $sensor [ 'sensor_id' ];
2020-09-21 15:59:34 +02:00
dbDelete ( 'sensors_to_state_indexes' , '`sensor_id` = ?' , [ $sensor_id ]);
2017-02-23 23:03:07 +00:00
}
2020-09-21 15:40:17 +02:00
$fields = [ 'device_id' , 'host' ];
2018-04-11 10:15:13 -05:00
$db_name = dbFetchCell ( 'SELECT DATABASE()' );
2016-08-28 12:32:58 -05:00
foreach ( $fields as $field ) {
2020-09-21 15:59:34 +02:00
foreach ( dbFetch ( 'SELECT TABLE_NAME FROM information_schema.columns WHERE table_schema = ? AND column_name = ?' , [ $db_name , $field ]) as $table ) {
2020-02-18 16:16:04 +01:00
$table = $table [ 'TABLE_NAME' ];
2020-09-21 15:40:17 +02:00
$entries = ( int ) dbDelete ( $table , "` $field ` = ?" , [ $id ]);
2016-08-28 12:32:58 -05:00
if ( $entries > 0 && $debug === true ) {
2015-07-13 20:10:26 +02:00
$ret .= " $field @ $table = # $entries \n " ;
2012-05-20 23:19:28 +00:00
}
2012-05-09 16:18:23 +00:00
}
2015-07-13 20:10:26 +02:00
}
2021-03-28 17:25:30 -05:00
$ex = shell_exec ( "bash -c '( [ ! -d " . trim ( Rrd :: dirFromHost ( $host )) . ' ] || rm -vrf ' . trim ( Rrd :: dirFromHost ( $host )) . " 2>&1 ) && echo -n OK'" );
2016-08-28 12:32:58 -05:00
$tmp = explode ( " \n " , $ex );
2020-09-21 15:59:34 +02:00
if ( $tmp [ sizeof ( $tmp ) - 1 ] != 'OK' ) {
2015-07-13 20:10:26 +02:00
$ret .= "Could not remove files: \n $ex \n " ;
}
$ret .= "Removed device $host \n " ;
2017-02-13 00:41:05 +02:00
log_event ( "Device $host has been removed" , 0 , 'system' , 3 );
2017-04-28 04:31:48 +01:00
oxidized_reload_nodes ();
2020-09-21 15:40:17 +02:00
2015-07-13 20:10:26 +02:00
return $ret ;
}
2016-08-01 22:01:01 -05:00
/**
* Add a device to LibreNMS
*
* @param string $host dns name or ip address
* @param string $snmp_version If this is empty, try v2c,v3,v1. Otherwise, use this specific version.
* @param string $port the port to connect to for snmp
* @param string $transport udp or tcp
* @param string $poller_group the poller group this device will belong to
2020-09-21 15:40:17 +02:00
* @param bool $force_add add even if the device isn't reachable
2016-08-01 22:01:01 -05:00
* @param string $port_assoc_mode snmp field to use to determine unique ports
2017-10-28 05:59:25 +02:00
* @param array $additional an array with additional parameters to take into consideration when adding devices
2016-08-07 12:16:40 -05:00
*
* @return int returns the device_id of the added device
*
* @throws HostExistsException This hostname already exists
* @throws HostIpExistsException We already have a host with this IP
* @throws HostUnreachableException We could not reach this device is some way
* @throws HostUnreachablePingException We could not ping the device
* @throws InvalidPortAssocModeException The given port association mode was invalid
* @throws SnmpVersionUnsupportedException The given snmp version was invalid
2016-08-01 22:01:01 -05:00
*/
2020-09-21 15:40:17 +02:00
function addHost ( $host , $snmp_version = '' , $port = '161' , $transport = 'udp' , $poller_group = '0' , $force_add = false , $port_assoc_mode = 'ifIndex' , $additional = [])
2016-08-28 12:32:58 -05:00
{
2015-07-13 20:10:26 +02:00
// Test Database Exists
2017-07-16 22:01:07 +01:00
if ( host_exists ( $host )) {
2016-08-07 12:16:40 -05:00
throw new HostExistsException ( "Already have host $host " );
2016-08-01 22:01:01 -05:00
}
2016-01-21 22:05:11 +01:00
2016-08-01 22:01:01 -05:00
// Valid port assoc mode
2020-09-21 15:40:17 +02:00
if ( ! in_array ( $port_assoc_mode , get_port_assoc_modes ())) {
2016-08-07 12:16:40 -05:00
throw new InvalidPortAssocModeException ( "Invalid port association_mode ' $port_assoc_mode '. Valid modes are: " . join ( ', ' , get_port_assoc_modes ()));
2016-08-01 22:01:01 -05:00
}
2015-07-13 20:10:26 +02:00
2016-08-01 22:01:01 -05:00
// check if we have the host by IP
2020-04-08 11:47:40 +02:00
$overwrite_ip = null ;
2020-09-21 15:40:17 +02:00
if ( ! empty ( $additional [ 'overwrite_ip' ])) {
2020-04-08 11:47:40 +02:00
$overwrite_ip = $additional [ 'overwrite_ip' ];
2020-01-30 13:20:30 +01:00
$ip = $overwrite_ip ;
} elseif ( Config :: get ( 'addhost_alwayscheckip' ) === true ) {
2016-08-01 22:01:01 -05:00
$ip = gethostbyname ( $host );
} else {
$ip = $host ;
}
2019-01-19 12:46:03 -06:00
if ( $force_add !== true && $device = device_has_ip ( $ip )) {
$message = "Cannot add $host , already have device with this IP $ip " ;
if ( $ip != $device -> hostname ) {
$message .= " ( $device->hostname )" ;
}
$message .= '. You may force add to ignore this.' ;
throw new HostIpExistsException ( $message );
2016-08-01 22:01:01 -05:00
}
// Test reachability
2020-09-21 15:40:17 +02:00
if ( ! $force_add ) {
2016-08-09 15:00:12 -05:00
$address_family = snmpTransportToAddressFamily ( $transport );
2020-01-30 13:20:30 +01:00
$ping_result = isPingable ( $ip , $address_family );
2020-09-21 15:40:17 +02:00
if ( ! $ping_result [ 'result' ]) {
2016-08-09 15:00:12 -05:00
throw new HostUnreachablePingException ( "Could not ping $host " );
}
2016-08-01 22:01:01 -05:00
}
2015-07-13 20:10:26 +02:00
2016-08-01 22:01:01 -05:00
// if $snmpver isn't set, try each version of snmp
if ( empty ( $snmp_version )) {
2018-04-12 04:35:51 +01:00
$snmpvers = Config :: get ( 'snmp.version' );
2016-08-01 22:01:01 -05:00
} else {
2020-09-21 15:40:17 +02:00
$snmpvers = [ $snmp_version ];
2016-08-01 22:01:01 -05:00
}
2017-10-28 05:59:25 +02:00
if ( isset ( $additional [ 'snmp_disable' ]) && $additional [ 'snmp_disable' ] == 1 ) {
2020-09-21 15:40:17 +02:00
return createHost ( $host , '' , $snmp_version , $port , $transport , [], $poller_group , 1 , true , $overwrite_ip , $additional );
2017-10-28 05:59:25 +02:00
}
2017-05-18 22:03:55 +02:00
$host_unreachable_exception = new HostUnreachableException ( "Could not connect to $host , please check the snmp details and snmp reachability" );
2016-08-01 22:01:01 -05:00
// try different snmp variables to add the device
foreach ( $snmpvers as $snmpver ) {
2020-09-21 15:59:34 +02:00
if ( $snmpver === 'v3' ) {
2016-08-01 22:01:01 -05:00
// Try each set of parameters from config
2019-06-23 00:29:12 -05:00
foreach ( Config :: get ( 'snmp.v3' ) as $v3 ) {
2020-04-08 11:47:40 +02:00
$device = deviceArray ( $host , null , $snmpver , $port , $transport , $v3 , $port_assoc_mode , $overwrite_ip );
2016-09-16 09:40:00 +01:00
if ( $force_add === true || isSNMPable ( $device )) {
2020-01-30 13:20:30 +01:00
return createHost ( $host , null , $snmpver , $port , $transport , $v3 , $poller_group , $port_assoc_mode , $force_add , $overwrite_ip );
2016-08-01 22:01:01 -05:00
} else {
2020-09-21 15:59:34 +02:00
$host_unreachable_exception -> addReason ( "SNMP $snmpver : No reply with credentials " . $v3 [ 'authname' ] . '/' . $v3 [ 'authlevel' ]);
2015-07-13 20:10:26 +02:00
}
2011-09-08 12:24:18 +00:00
}
2020-09-21 15:59:34 +02:00
} elseif ( $snmpver === 'v2c' || $snmpver === 'v1' ) {
2016-08-01 22:01:01 -05:00
// try each community from config
2019-06-23 00:29:12 -05:00
foreach ( Config :: get ( 'snmp.community' ) as $community ) {
2020-04-08 11:47:40 +02:00
$device = deviceArray ( $host , $community , $snmpver , $port , $transport , null , $port_assoc_mode , $overwrite_ip );
2016-08-07 12:16:40 -05:00
2016-09-16 09:40:00 +01:00
if ( $force_add === true || isSNMPable ( $device )) {
2020-09-21 15:40:17 +02:00
return createHost ( $host , $community , $snmpver , $port , $transport , [], $poller_group , $port_assoc_mode , $force_add , $overwrite_ip );
2016-08-01 22:01:01 -05:00
} else {
2016-08-07 12:16:40 -05:00
$host_unreachable_exception -> addReason ( "SNMP $snmpver : No reply with community $community " );
2015-07-13 20:10:26 +02:00
}
2012-05-09 16:18:23 +00:00
}
2016-08-01 22:01:01 -05:00
} else {
2016-08-07 12:16:40 -05:00
throw new SnmpVersionUnsupportedException ( "Unsupported SNMP Version \" $snmpver \" , must be v1, v2c, or v3" );
2015-06-11 19:34:11 +01:00
}
}
2017-10-28 05:59:25 +02:00
if ( isset ( $additional [ 'ping_fallback' ]) && $additional [ 'ping_fallback' ] == 1 ) {
$additional [ 'snmp_disable' ] = 1 ;
2020-09-21 15:59:34 +02:00
$additional [ 'os' ] = 'ping' ;
2020-09-21 15:40:17 +02:00
return createHost ( $host , '' , $snmp_version , $port , $transport , [], $poller_group , 1 , true , $overwrite_ip , $additional );
2017-10-28 05:59:25 +02:00
}
2016-08-07 12:16:40 -05:00
throw $host_unreachable_exception ;
2007-04-03 14:10:23 +00:00
}
2020-09-21 15:40:17 +02:00
function deviceArray ( $host , $community , $snmpver , $port = 161 , $transport = 'udp' , $v3 = [], $port_assoc_mode = 'ifIndex' , $overwrite_ip = null )
2016-08-28 12:32:58 -05:00
{
2020-09-21 15:40:17 +02:00
$device = [];
2015-07-13 20:10:26 +02:00
$device [ 'hostname' ] = $host ;
2020-04-08 11:47:40 +02:00
$device [ 'overwrite_ip' ] = $overwrite_ip ;
2015-07-13 20:10:26 +02:00
$device [ 'port' ] = $port ;
$device [ 'transport' ] = $transport ;
2016-01-21 22:05:11 +01:00
/* Get port_assoc_mode id if neccessary
* We can work with names of IDs here */
2016-08-28 12:32:58 -05:00
if ( ! is_int ( $port_assoc_mode )) {
$port_assoc_mode = get_port_assoc_mode_id ( $port_assoc_mode );
}
2016-01-21 22:05:11 +01:00
$device [ 'port_association_mode' ] = $port_assoc_mode ;
2015-07-13 20:10:26 +02:00
$device [ 'snmpver' ] = $snmpver ;
2020-09-21 15:59:34 +02:00
if ( $snmpver === 'v2c' or $snmpver === 'v1' ) {
2015-07-13 20:10:26 +02:00
$device [ 'community' ] = $community ;
2020-09-21 15:59:34 +02:00
} elseif ( $snmpver === 'v3' ) {
2020-09-21 15:40:17 +02:00
$device [ 'authlevel' ] = $v3 [ 'authlevel' ];
$device [ 'authname' ] = $v3 [ 'authname' ];
$device [ 'authpass' ] = $v3 [ 'authpass' ];
$device [ 'authalgo' ] = $v3 [ 'authalgo' ];
2015-07-13 20:10:26 +02:00
$device [ 'cryptopass' ] = $v3 [ 'cryptopass' ];
$device [ 'cryptoalgo' ] = $v3 [ 'cryptoalgo' ];
}
return $device ;
2020-04-08 11:47:40 +02:00
} //end deviceArray()
2011-03-15 16:12:44 +00:00
2016-08-28 12:32:58 -05:00
function isSNMPable ( $device )
{
2017-02-23 22:47:18 +00:00
$pos = snmp_check ( $device );
if ( $pos === true ) {
2015-07-13 20:10:26 +02:00
return true ;
2017-02-23 22:47:18 +00:00
} else {
2020-09-21 15:59:34 +02:00
$pos = snmp_get ( $device , 'sysObjectID.0' , '-Oqv' , 'SNMPv2-MIB' );
2017-02-23 22:47:18 +00:00
if ( $pos === '' || $pos === false ) {
return false ;
} else {
return true ;
}
2015-07-13 20:10:26 +02:00
}
2007-04-03 14:10:23 +00:00
}
2015-09-24 02:07:15 +02:00
/**
* Check if the given host responds to ICMP echo requests ("pings").
*
* @param string $hostname The hostname or IP address to send ping requests to.
2018-08-11 17:34:52 -05:00
* @param string $address_family The address family ('ipv4' or 'ipv6') to use. Defaults to IPv4.
* 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.
2015-10-17 19:11:21 +00:00
* @param array $attribs The device attributes
2015-09-24 02:07:15 +02:00
*
2016-08-01 22:01:01 -05:00
* @return array 'result' => bool pingable, 'last_ping_timetaken' => int time for last ping, 'db' => fping results
2015-09-24 02:07:15 +02:00
*/
2018-08-11 17:34:52 -05:00
function isPingable ( $hostname , $address_family = 'ipv4' , $attribs = [])
2016-08-28 12:32:58 -05:00
{
2018-08-11 17:34:52 -05:00
if ( can_ping_device ( $attribs ) !== true ) {
return [
'result' => true ,
2020-09-21 15:40:17 +02:00
'last_ping_timetaken' => 0 ,
2018-08-11 17:34:52 -05:00
];
}
2020-05-19 22:08:41 -05:00
$status = app () -> make ( Fping :: class ) -> ping (
2018-08-11 17:34:52 -05:00
$hostname ,
Config :: get ( 'fping_options.count' , 3 ),
Config :: get ( 'fping_options.interval' , 500 ),
Config :: get ( 'fping_options.timeout' , 500 ),
$address_family
);
2015-07-13 20:10:26 +02:00
2019-10-16 08:36:54 +00:00
if ( $status [ 'dup' ] > 0 ) {
Log :: event ( 'Duplicate ICMP response detected! This could indicate a network issue.' , getidbyname ( $hostname ), 'icmp' , 4 );
$status [ 'exitcode' ] = 0 ; // when duplicate is detected fping returns 1. The device is up, but there is another issue. Clue admins in with above event.
}
2018-08-11 17:34:52 -05:00
return [
'result' => ( $status [ 'exitcode' ] == 0 && $status [ 'loss' ] < 100 ),
'last_ping_timetaken' => $status [ 'avg' ],
2020-09-21 15:40:17 +02:00
'db' => array_intersect_key ( $status , array_flip ([ 'xmt' , 'rcv' , 'loss' , 'min' , 'max' , 'avg' ])),
2018-08-11 17:34:52 -05:00
];
2007-04-03 14:10:23 +00:00
}
2016-08-28 12:32:58 -05:00
function getpollergroup ( $poller_group = '0' )
{
2015-07-13 20:10:26 +02:00
//Is poller group an integer
if ( is_int ( $poller_group ) || ctype_digit ( $poller_group )) {
return $poller_group ;
2016-08-28 12:32:58 -05:00
} else {
2015-07-13 20:10:26 +02:00
//Check if it contains a comma
2020-09-21 15:40:17 +02:00
if ( strpos ( $poller_group , ',' ) !== false ) {
2015-07-13 20:10:26 +02:00
//If it has a comma use the first element as the poller group
2020-09-21 15:40:17 +02:00
$poller_group_array = explode ( ',' , $poller_group );
2015-07-13 20:10:26 +02:00
return getpollergroup ( $poller_group_array [ 0 ]);
2016-08-28 12:32:58 -05:00
} else {
2019-06-23 00:29:12 -05:00
if ( Config :: get ( 'distributed_poller_group' )) {
2015-07-13 20:10:26 +02:00
//If not use the poller's group from the config
2019-06-23 00:29:12 -05:00
return getpollergroup ( Config :: get ( 'distributed_poller_group' ));
2016-08-28 12:32:58 -05:00
} else {
2015-07-13 20:10:26 +02:00
//If all else fails use default
return '0' ;
}
}
}
2015-06-22 12:37:00 +02:00
}
2017-02-22 03:12:31 -06:00
/**
* Add a host to the database
*
* @param string $host The IP or hostname to add
* @param string $community The snmp community
* @param string $snmpver snmp version: v1 | v2c | v3
* @param int $port SNMP port number
* @param string $transport SNMP transport: udp | udp6 | udp | tcp6
* @param array $v3 SNMPv3 settings required array keys: authlevel, authname, authpass, authalgo, cryptopass, cryptoalgo
* @param int $poller_group distributed poller group to assign this host to
* @param string $port_assoc_mode field to use to identify ports: ifIndex, ifName, ifDescr, ifAlias
* @param bool $force_add Do not detect the host os
2017-10-28 05:59:25 +02:00
* @param array $additional an array with additional parameters to take into consideration when adding devices
2017-02-22 03:12:31 -06:00
* @return int the id of the added host
* @throws HostExistsException Throws this exception if the host already exists
* @throws Exception Throws this exception if insertion into the database fails
*/
function createHost (
$host ,
$community ,
$snmpver ,
$port = 161 ,
$transport = 'udp' ,
2020-09-21 15:40:17 +02:00
$v3 = [],
2017-02-22 03:12:31 -06:00
$poller_group = 0 ,
$port_assoc_mode = 'ifIndex' ,
2017-10-28 05:59:25 +02:00
$force_add = false ,
2020-01-30 13:20:30 +01:00
$overwrite_ip = null ,
2020-09-21 15:40:17 +02:00
$additional = []
2017-02-22 03:12:31 -06:00
) {
2015-07-13 20:10:26 +02:00
$host = trim ( strtolower ( $host ));
2020-09-21 15:40:17 +02:00
$poller_group = getpollergroup ( $poller_group );
2015-07-13 20:10:26 +02:00
2016-08-01 22:01:01 -05:00
/* Get port_assoc_mode id if necessary
2016-02-26 08:04:38 -06:00
* We can work with names of IDs here */
2016-08-28 12:32:58 -05:00
if ( ! is_int ( $port_assoc_mode )) {
$port_assoc_mode = get_port_assoc_mode_id ( $port_assoc_mode );
}
2016-02-26 08:04:38 -06:00
2020-09-21 15:40:17 +02:00
$device = [
2017-02-22 03:12:31 -06:00
'hostname' => $host ,
2020-01-30 13:20:30 +01:00
'overwrite_ip' => $overwrite_ip ,
2020-04-20 00:36:01 +02:00
'sysName' => $additional [ 'sysName' ] ?? $host ,
'os' => $additional [ 'os' ] ?? 'generic' ,
'hardware' => $additional [ 'hardware' ] ?? null ,
2015-07-13 20:10:26 +02:00
'community' => $community ,
'port' => $port ,
'transport' => $transport ,
'status' => '1' ,
'snmpver' => $snmpver ,
2015-08-11 11:38:05 +00:00
'poller_group' => $poller_group ,
'status_reason' => '' ,
2016-01-21 22:05:11 +01:00
'port_association_mode' => $port_assoc_mode ,
2020-04-20 00:36:01 +02:00
'snmp_disable' => $additional [ 'snmp_disable' ] ?? 0 ,
2020-09-21 15:40:17 +02:00
];
2012-05-09 16:18:23 +00:00
2017-02-22 03:12:31 -06:00
$device = array_merge ( $device , $v3 ); // merge v3 settings
2011-09-14 17:26:59 +00:00
2016-09-16 09:40:00 +01:00
if ( $force_add !== true ) {
2020-10-05 07:26:37 -05:00
$device [ 'os' ] = Core :: detectOS ( $device );
2011-03-15 16:12:44 +00:00
2020-09-21 15:59:34 +02:00
$snmphost = snmp_get ( $device , 'sysName.0' , '-Oqv' , 'SNMPv2-MIB' );
2017-07-16 22:01:07 +01:00
if ( host_exists ( $host , $snmphost )) {
throw new HostExistsException ( "Already have host $host ( $snmphost ) due to duplicate sysName" );
}
2010-02-07 22:39:02 +00:00
}
2016-08-01 22:01:01 -05:00
2017-02-22 03:12:31 -06:00
$device_id = dbInsert ( $device , 'devices' );
if ( $device_id ) {
return $device_id ;
}
2020-09-21 15:59:34 +02:00
throw new \Exception ( 'Failed to add host to the database, please run ./validate.php' );
2007-04-03 14:10:23 +00:00
}
2016-08-28 12:32:58 -05:00
function isDomainResolves ( $domain )
{
2017-01-24 15:56:51 -06:00
if ( gethostbyname ( $domain ) != $domain ) {
return true ;
}
$records = dns_get_record ( $domain ); // returns array or false
2020-09-21 15:40:17 +02:00
return ! empty ( $records );
2007-04-03 14:10:23 +00:00
}
2016-08-28 12:32:58 -05:00
function match_network ( $nets , $ip , $first = false )
{
2015-07-13 20:10:26 +02:00
$return = false ;
2020-09-21 15:40:17 +02:00
if ( ! is_array ( $nets )) {
$nets = [ $nets ];
2016-08-28 12:32:58 -05:00
}
2015-07-13 20:10:26 +02:00
foreach ( $nets as $net ) {
2016-08-28 12:32:58 -05:00
$rev = ( preg_match ( "/^\!/" , $net )) ? true : false ;
2020-09-21 15:59:34 +02:00
$net = preg_replace ( "/^\!/" , '' , $net );
2020-09-21 15:40:17 +02:00
$ip_arr = explode ( '/' , $net );
2015-07-13 20:10:26 +02:00
$net_long = ip2long ( $ip_arr [ 0 ]);
2020-09-21 15:40:17 +02:00
$x = ip2long ( $ip_arr [ 1 ]);
$mask = long2ip ( $x ) == $ip_arr [ 1 ] ? $x : 0xffffffff << ( 32 - $ip_arr [ 1 ]);
$ip_long = ip2long ( $ip );
2015-07-13 20:10:26 +02:00
if ( $rev ) {
if (( $ip_long & $mask ) == ( $net_long & $mask )) {
return false ;
}
2016-08-28 12:32:58 -05:00
} else {
2015-07-13 20:10:26 +02:00
if (( $ip_long & $mask ) == ( $net_long & $mask )) {
$return = true ;
}
if ( $first && $return ) {
return true ;
}
}
}
return $return ;
2007-04-03 14:10:23 +00:00
}
2017-08-08 14:14:58 -05:00
// FIXME port to LibreNMS\Util\IPv6 class
2016-08-28 12:32:58 -05:00
function snmp2ipv6 ( $ipv6_snmp )
{
2020-09-21 15:40:17 +02:00
// Workaround stupid Microsoft bug in Windows 2008 -- this is fixed length!
// < fenestro> "because whoever implemented this mib for Microsoft was ignorant of RFC 2578 section 7.7 (2)"
2018-01-18 14:54:38 -06:00
$ipv6 = array_slice ( explode ( '.' , $ipv6_snmp ), - 16 );
2020-09-21 15:40:17 +02:00
$ipv6_2 = [];
2011-04-21 17:11:58 +00:00
2016-08-28 12:32:58 -05:00
for ( $i = 0 ; $i <= 15 ; $i ++ ) {
2015-07-13 20:10:26 +02:00
$ipv6 [ $i ] = zeropad ( dechex ( $ipv6 [ $i ]));
}
2020-09-21 15:40:17 +02:00
for ( $i = 0 ; $i <= 15 ; $i += 2 ) {
$ipv6_2 [] = $ipv6 [ $i ] . $ipv6 [ $i + 1 ];
2015-07-13 20:10:26 +02:00
}
2011-03-08 17:12:43 +00:00
2016-08-28 12:32:58 -05:00
return implode ( ':' , $ipv6_2 );
2010-01-01 14:09:57 +00:00
}
2016-08-28 12:32:58 -05:00
function get_astext ( $asn )
{
2018-08-17 12:17:45 -05:00
global $cache ;
2015-07-13 20:10:26 +02:00
2018-08-17 12:17:45 -05:00
if ( Config :: has ( "astext. $asn " )) {
return Config :: get ( "astext. $asn " );
}
if ( isset ( $cache [ 'astext' ][ $asn ])) {
return $cache [ 'astext' ][ $asn ];
}
2018-09-10 21:40:41 -05:00
$result = @ dns_get_record ( "AS $asn .asn.cymru.com" , DNS_TXT );
2020-09-21 15:40:17 +02:00
if ( ! empty ( $result [ 0 ][ 'txt' ])) {
2018-08-17 12:17:45 -05:00
$txt = explode ( '|' , $result [ 0 ][ 'txt' ]);
$result = trim ( $txt [ 4 ], ' "' );
$cache [ 'astext' ][ $asn ] = $result ;
2020-09-21 15:40:17 +02:00
2018-08-17 12:17:45 -05:00
return $result ;
2015-07-13 20:10:26 +02:00
}
2018-08-17 12:17:45 -05:00
return '' ;
2010-01-03 20:13:10 +00:00
}
2010-01-07 16:50:52 +00:00
2017-03-12 08:05:31 -05:00
/**
* Log events to the event table
*
* @param string $text message describing the event
* @param array|int $device device array or device_id
* @param string $type brief category for this event. Examples: sensor, state, stp, system, temperature, interface
* @param int $severity 1: ok, 2: info, 3: notice, 4: warning, 5: critical, 0: unknown
* @param int $reference the id of the referenced entity. Supported types: interface
*/
2017-02-13 00:41:05 +02:00
function log_event ( $text , $device = null , $type = null , $severity = 2 , $reference = null )
2016-08-28 12:32:58 -05:00
{
2020-04-11 19:53:25 -05:00
// handle legacy device array
if ( is_array ( $device ) && isset ( $device [ 'device_id' ])) {
$device = $device [ 'device_id' ];
}
Log :: event ( $text , $device , $type , $severity , $reference );
2011-03-08 17:12:43 +00:00
}
2010-02-17 11:02:18 +00:00
2012-04-26 12:11:03 +00:00
// Parse string with emails. Return array with email (as key) and name (as value)
2016-08-28 12:32:58 -05:00
function parse_email ( $emails )
{
2020-09-21 15:40:17 +02:00
$result = [];
2015-07-13 20:10:26 +02:00
$regex = '/^[\"\']?([^\"\']+)[\"\']?\s{0,}<([^@]+@[^>]+)>$/' ;
if ( is_string ( $emails )) {
$emails = preg_split ( '/[,;]\s{0,}/' , $emails );
foreach ( $emails as $email ) {
if ( preg_match ( $regex , $email , $out , PREG_OFFSET_CAPTURE )) {
$result [ $out [ 2 ][ 0 ]] = $out [ 1 ][ 0 ];
2016-08-28 12:32:58 -05:00
} else {
2020-09-21 15:59:34 +02:00
if ( strpos ( $email , '@' )) {
2018-05-14 19:56:43 +01:00
$from_name = Config :: get ( 'email_user' );
$result [ $email ] = $from_name ;
2015-07-13 20:10:26 +02:00
}
}
}
2016-08-28 12:32:58 -05:00
} else {
2015-07-13 20:10:26 +02:00
// Return FALSE if input not string
2016-08-28 12:32:58 -05:00
return false ;
2015-07-13 20:10:26 +02:00
}
2020-09-21 15:40:17 +02:00
2015-07-13 20:10:26 +02:00
return $result ;
2012-04-26 12:11:03 +00:00
}
2016-08-28 12:32:58 -05:00
function send_mail ( $emails , $subject , $message , $html = false )
{
if ( is_array ( $emails ) || ( $emails = parse_email ( $emails ))) {
2018-08-31 20:48:04 -05:00
d_echo ( "Attempting to email $subject to: " . implode ( '; ' , array_keys ( $emails )) . PHP_EOL );
2018-09-02 15:45:39 -07:00
$mail = new PHPMailer ( true );
try {
$mail -> Hostname = php_uname ( 'n' );
2017-03-03 18:22:33 +00:00
2019-06-23 00:29:12 -05:00
foreach ( parse_email ( Config :: get ( 'email_from' )) as $from => $from_name ) {
2018-09-02 15:45:39 -07:00
$mail -> setFrom ( $from , $from_name );
}
foreach ( $emails as $email => $email_name ) {
$mail -> addAddress ( $email , $email_name );
}
$mail -> Subject = $subject ;
2019-10-16 21:22:05 +00:00
$mail -> XMailer = Config :: get ( 'project_name' );
2018-09-02 15:45:39 -07:00
$mail -> CharSet = 'utf-8' ;
$mail -> WordWrap = 76 ;
$mail -> Body = $message ;
if ( $html ) {
$mail -> isHTML ( true );
}
2019-06-23 00:29:12 -05:00
switch ( strtolower ( trim ( Config :: get ( 'email_backend' )))) {
2018-09-02 15:45:39 -07:00
case 'sendmail' :
$mail -> Mailer = 'sendmail' ;
2019-06-23 00:29:12 -05:00
$mail -> Sendmail = Config :: get ( 'email_sendmail_path' );
2018-09-02 15:45:39 -07:00
break ;
case 'smtp' :
$mail -> isSMTP ();
2019-06-23 00:29:12 -05:00
$mail -> Host = Config :: get ( 'email_smtp_host' );
$mail -> Timeout = Config :: get ( 'email_smtp_timeout' );
$mail -> SMTPAuth = Config :: get ( 'email_smtp_auth' );
$mail -> SMTPSecure = Config :: get ( 'email_smtp_secure' );
$mail -> Port = Config :: get ( 'email_smtp_port' );
$mail -> Username = Config :: get ( 'email_smtp_username' );
$mail -> Password = Config :: get ( 'email_smtp_password' );
$mail -> SMTPAutoTLS = Config :: get ( 'email_auto_tls' );
2020-09-21 15:40:17 +02:00
$mail -> SMTPDebug = false ;
2018-09-02 15:45:39 -07:00
break ;
default :
$mail -> Mailer = 'mail' ;
break ;
}
$mail -> send ();
2020-09-21 15:40:17 +02:00
2018-09-02 15:45:39 -07:00
return true ;
2019-03-12 23:59:03 -05:00
} catch ( \PHPMailer\PHPMailer\Exception $e ) {
2018-09-02 15:45:39 -07:00
return $e -> errorMessage ();
} catch ( Exception $e ) {
return $e -> getMessage ();
2015-07-13 20:10:26 +02:00
}
}
2018-07-31 15:53:03 -05:00
2020-09-21 15:59:34 +02:00
return 'No contacts found' ;
2014-03-07 22:30:07 +01:00
}
2016-08-28 12:32:58 -05:00
function hex2str ( $hex )
{
2020-09-21 15:40:17 +02:00
$string = '' ;
2011-03-11 18:03:49 +00:00
2020-09-21 15:40:17 +02:00
for ( $i = 0 ; $i < strlen ( $hex ) - 1 ; $i += 2 ) {
2017-02-12 07:20:06 -06:00
$string .= chr ( hexdec ( substr ( $hex , $i , 2 )));
2015-07-13 20:10:26 +02:00
}
2011-03-11 18:03:49 +00:00
2015-07-13 20:10:26 +02:00
return $string ;
2010-06-18 16:38:41 +00:00
}
2010-07-05 15:27:01 +00:00
2020-09-21 15:40:17 +02:00
// Convert an SNMP hex string to regular string
2016-08-28 12:32:58 -05:00
function snmp_hexstring ( $hex )
{
return hex2str ( str_replace ( ' ' , '' , str_replace ( ' 00' , '' , $hex )));
2010-07-05 15:27:01 +00:00
}
2020-09-21 15:40:17 +02:00
// Check if the supplied string is an SNMP hex string
2016-08-28 12:32:58 -05:00
function isHexString ( $str )
{
2020-09-21 15:59:34 +02:00
return ( bool ) preg_match ( '/^[a-f0-9][a-f0-9]( [a-f0-9][a-f0-9])*$/is' , trim ( $str ));
2010-07-05 15:27:01 +00:00
}
2020-09-21 15:40:17 +02:00
// Include all .inc.php files in $dir
2020-09-21 15:59:34 +02:00
function include_dir ( $dir , $regex = '' )
2016-08-28 12:32:58 -05:00
{
2019-06-23 00:29:12 -05:00
global $device , $valid ;
2011-09-16 10:31:48 +00:00
2020-09-21 15:59:34 +02:00
if ( $regex == '' ) {
2015-07-13 20:10:26 +02:00
$regex = "/\.inc\.php$/" ;
}
2011-03-23 09:54:56 +00:00
2019-06-23 00:29:12 -05:00
if ( $handle = opendir ( Config :: get ( 'install_dir' ) . '/' . $dir )) {
2015-07-13 20:10:26 +02:00
while ( false !== ( $file = readdir ( $handle ))) {
2019-06-23 00:29:12 -05:00
if ( filetype ( Config :: get ( 'install_dir' ) . '/' . $dir . '/' . $file ) == 'file' && preg_match ( $regex , $file )) {
2020-09-21 15:59:34 +02:00
d_echo ( 'Including: ' . Config :: get ( 'install_dir' ) . '/' . $dir . '/' . $file . " \n " );
2012-04-05 16:44:58 +00:00
2020-09-21 15:40:17 +02:00
include Config :: get ( 'install_dir' ) . '/' . $dir . '/' . $file ;
2015-07-13 20:10:26 +02:00
}
}
2010-07-09 22:38:46 +00:00
2015-07-13 20:10:26 +02:00
closedir ( $handle );
}
2010-07-09 22:38:46 +00:00
}
2017-09-21 12:02:01 -05:00
/**
* Check if port is valid to poll.
2020-01-24 19:58:01 +08:00
* Settings: empty_ifdescr, good_if, bad_if, bad_if_regexp, bad_ifname_regexp, bad_ifalias_regexp, bad_iftype, bad_ifoperstatus
2017-09-21 12:02:01 -05:00
*
* @param array $port
* @param array $device
* @return bool
*/
2016-08-28 12:32:58 -05:00
function is_port_valid ( $port , $device )
{
2017-09-21 12:02:01 -05:00
// check empty values first
if ( empty ( $port [ 'ifDescr' ])) {
2017-02-03 19:49:40 +00:00
// If these are all empty, we are just going to show blank names in the ui
2017-09-21 12:02:01 -05:00
if ( empty ( $port [ 'ifAlias' ]) && empty ( $port [ 'ifName' ])) {
2018-08-04 16:10:57 -05:00
d_echo ( "ignored: empty ifDescr, ifAlias and ifName \n " );
2020-09-21 15:40:17 +02:00
2017-09-21 12:02:01 -05:00
return false ;
2015-07-13 20:10:26 +02:00
}
2017-09-21 12:02:01 -05:00
// ifDescr should not be empty unless it is explicitly allowed
2020-09-21 15:40:17 +02:00
if ( ! Config :: getOsSetting ( $device [ 'os' ], 'empty_ifdescr' , Config :: get ( 'empty_ifdescr' , false ))) {
2018-08-04 16:10:57 -05:00
d_echo ( "ignored: empty ifDescr \n " );
2020-09-21 15:40:17 +02:00
2017-09-21 12:02:01 -05:00
return false ;
2015-07-13 20:10:26 +02:00
}
2017-09-21 12:02:01 -05:00
}
$ifDescr = $port [ 'ifDescr' ];
2020-09-21 15:40:17 +02:00
$ifName = $port [ 'ifName' ];
2017-09-21 12:02:01 -05:00
$ifAlias = $port [ 'ifAlias' ];
2020-09-21 15:40:17 +02:00
$ifType = $port [ 'ifType' ];
2020-01-24 19:58:01 +08:00
$ifOperStatus = $port [ 'ifOperStatus' ];
2017-09-21 12:02:01 -05:00
2020-05-19 14:35:32 -05:00
if ( str_i_contains ( $ifDescr , Config :: getOsSetting ( $device [ 'os' ], 'good_if' , Config :: get ( 'good_if' )))) {
2017-09-21 12:02:01 -05:00
return true ;
}
foreach ( Config :: getCombined ( $device [ 'os' ], 'bad_if' ) as $bi ) {
2018-01-29 15:58:21 -06:00
if ( str_i_contains ( $ifDescr , $bi )) {
2017-09-21 12:02:01 -05:00
d_echo ( "ignored by ifDescr: $ifDescr (matched: $bi ) \n " );
2020-09-21 15:40:17 +02:00
2017-09-21 12:02:01 -05:00
return false ;
2015-07-13 20:10:26 +02:00
}
2017-09-21 12:02:01 -05:00
}
foreach ( Config :: getCombined ( $device [ 'os' ], 'bad_if_regexp' ) as $bir ) {
2020-09-21 15:59:34 +02:00
if ( preg_match ( $bir . 'i' , $ifDescr )) {
2017-09-21 12:02:01 -05:00
d_echo ( "ignored by ifDescr: $ifDescr (matched: $bir ) \n " );
2020-09-21 15:40:17 +02:00
2017-09-21 12:02:01 -05:00
return false ;
2017-09-17 08:21:28 +01:00
}
2017-09-21 12:02:01 -05:00
}
foreach ( Config :: getCombined ( $device [ 'os' ], 'bad_ifname_regexp' ) as $bnr ) {
2020-09-21 15:59:34 +02:00
if ( preg_match ( $bnr . 'i' , $ifName )) {
2017-09-21 12:02:01 -05:00
d_echo ( "ignored by ifName: $ifName (matched: $bnr ) \n " );
2020-09-21 15:40:17 +02:00
2017-09-21 12:02:01 -05:00
return false ;
2017-09-17 08:21:28 +01:00
}
2017-09-21 12:02:01 -05:00
}
foreach ( Config :: getCombined ( $device [ 'os' ], 'bad_ifalias_regexp' ) as $bar ) {
2020-09-21 15:59:34 +02:00
if ( preg_match ( $bar . 'i' , $ifAlias )) {
2017-09-21 12:02:01 -05:00
d_echo ( "ignored by ifName: $ifAlias (matched: $bar ) \n " );
2020-09-21 15:40:17 +02:00
2017-09-21 12:02:01 -05:00
return false ;
2017-09-17 08:21:28 +01:00
}
2017-09-21 12:02:01 -05:00
}
foreach ( Config :: getCombined ( $device [ 'os' ], 'bad_iftype' ) as $bt ) {
2020-04-17 17:37:56 -05:00
if ( Str :: contains ( $ifType , $bt )) {
2017-09-21 12:02:01 -05:00
d_echo ( "ignored by ifType: $ifType (matched: $bt ) \n " );
2020-09-21 15:40:17 +02:00
2017-09-21 12:02:01 -05:00
return false ;
2012-03-29 12:05:45 +00:00
}
}
2011-05-18 23:08:45 +00:00
2020-01-24 19:58:01 +08:00
foreach ( Config :: getCombined ( $device [ 'os' ], 'bad_ifoperstatus' ) as $bos ) {
2020-04-17 17:37:56 -05:00
if ( Str :: contains ( $ifOperStatus , $bos )) {
2020-01-24 19:58:01 +08:00
d_echo ( "ignored by ifOperStatus: $ifOperStatus (matched: $bos ) \n " );
2020-09-21 15:40:17 +02:00
2020-01-24 19:58:01 +08:00
return false ;
}
}
2017-09-21 12:02:01 -05:00
return true ;
2011-05-18 23:08:45 +00:00
}
2019-01-19 18:48:30 +01:00
/**
* Try to fill in data for ifDescr, ifName, and ifAlias if devices do not provide them.
* Will not fill ifAlias if the user has overridden it
*
* @param array $port
* @param array $device
*/
function port_fill_missing ( & $port , $device )
{
// When devices do not provide data, populate with other data if available
if ( $port [ 'ifDescr' ] == '' || $port [ 'ifDescr' ] == null ) {
$port [ 'ifDescr' ] = $port [ 'ifName' ];
d_echo ( ' Using ifName as ifDescr' );
}
2020-09-21 15:40:17 +02:00
if ( ! empty ( $device [ 'attribs' ][ 'ifName:' . $port [ 'ifName' ]])) {
2019-01-19 18:48:30 +01:00
// ifAlias overridden by user, don't update it
unset ( $port [ 'ifAlias' ]);
d_echo ( ' ifAlias overriden by user' );
} elseif ( $port [ 'ifAlias' ] == '' || $port [ 'ifAlias' ] == null ) {
$port [ 'ifAlias' ] = $port [ 'ifDescr' ];
d_echo ( ' Using ifDescr as ifAlias' );
}
if ( $port [ 'ifName' ] == '' || $port [ 'ifName' ] == null ) {
$port [ 'ifName' ] = $port [ 'ifDescr' ];
d_echo ( ' Using ifDescr as ifName' );
}
}
2016-08-28 12:32:58 -05:00
function scan_new_plugins ()
{
2015-07-13 20:10:26 +02:00
$installed = 0 ; // Track how many plugins we install.
2019-06-23 00:29:12 -05:00
if ( file_exists ( Config :: get ( 'plugin_dir' ))) {
$plugin_files = scandir ( Config :: get ( 'plugin_dir' ));
2016-08-28 12:32:58 -05:00
foreach ( $plugin_files as $name ) {
2019-06-23 00:29:12 -05:00
if ( is_dir ( Config :: get ( 'plugin_dir' ) . '/' . $name )) {
2016-08-28 12:32:58 -05:00
if ( $name != '.' && $name != '..' ) {
2019-06-23 00:29:12 -05:00
if ( is_file ( Config :: get ( 'plugin_dir' ) . '/' . $name . '/' . $name . '.php' ) && is_file ( Config :: get ( 'plugin_dir' ) . '/' . $name . '/' . $name . '.inc.php' )) {
2020-09-21 15:59:34 +02:00
$plugin_id = dbFetchRow ( 'SELECT `plugin_id` FROM `plugins` WHERE `plugin_name` = ?' , [ $name ]);
2016-08-28 12:32:58 -05:00
if ( empty ( $plugin_id )) {
2019-08-27 19:47:04 +02:00
if ( dbInsert ([ 'plugin_name' => $name , 'plugin_active' => '0' ], 'plugins' )) {
2015-07-13 20:10:26 +02:00
$installed ++ ;
}
}
}
}
2014-02-25 12:51:07 +00:00
}
}
}
2019-08-27 19:47:04 +02:00
return $installed ;
}
function scan_removed_plugins ()
{
2020-09-21 15:40:17 +02:00
$removed = 0 ; // Track how many plugins will be removed from database
2019-08-27 19:47:04 +02:00
if ( file_exists ( Config :: get ( 'plugin_dir' ))) {
$plugin_files = scandir ( Config :: get ( 'plugin_dir' ));
2020-09-21 15:59:34 +02:00
$installed_plugins = dbFetchColumn ( 'SELECT `plugin_name` FROM `plugins`' );
2019-08-27 19:47:04 +02:00
foreach ( $installed_plugins as $name ) {
if ( in_array ( $name , $plugin_files )) {
continue ;
}
2020-09-21 15:59:34 +02:00
if ( dbDelete ( 'plugins' , '`plugin_name` = ?' , $name )) {
2019-08-27 19:47:04 +02:00
$removed ++ ;
}
}
}
2020-09-21 15:40:17 +02:00
return $removed ;
2014-02-25 12:51:07 +00:00
}
2016-08-28 12:32:58 -05:00
function validate_device_id ( $id )
{
2020-09-21 15:40:17 +02:00
if ( empty ( $id ) || ! is_numeric ( $id )) {
2015-07-13 20:10:26 +02:00
$return = false ;
2016-08-28 12:32:58 -05:00
} else {
2020-09-21 15:59:34 +02:00
$device_id = dbFetchCell ( 'SELECT `device_id` FROM `devices` WHERE `device_id` = ?' , [ $id ]);
2016-08-28 12:32:58 -05:00
if ( $device_id == $id ) {
2015-07-13 20:10:26 +02:00
$return = true ;
2016-08-28 12:32:58 -05:00
} else {
2015-07-13 20:10:26 +02:00
$return = false ;
}
}
2020-09-21 15:40:17 +02:00
return $return ;
2014-06-24 16:14:42 +01:00
}
2014-09-17 21:03:02 +01:00
2016-08-28 12:32:58 -05:00
function convert_delay ( $delay )
{
$delay = preg_replace ( '/\s/' , '' , $delay );
if ( strstr ( $delay , 'm' , true )) {
2014-11-30 17:49:52 +00:00
$delay_sec = $delay * 60 ;
2016-08-28 12:32:58 -05:00
} elseif ( strstr ( $delay , 'h' , true )) {
2014-11-30 17:49:52 +00:00
$delay_sec = $delay * 3600 ;
2016-08-28 12:32:58 -05:00
} elseif ( strstr ( $delay , 'd' , true )) {
2014-11-30 17:49:52 +00:00
$delay_sec = $delay * 86400 ;
2016-08-28 12:32:58 -05:00
} elseif ( is_numeric ( $delay )) {
2014-11-30 17:49:52 +00:00
$delay_sec = $delay ;
2016-08-28 12:32:58 -05:00
} else {
2015-02-26 01:25:25 +00:00
$delay_sec = 300 ;
2014-11-30 17:49:52 +00:00
}
2020-09-21 15:40:17 +02:00
return $delay_sec ;
2014-11-30 17:49:52 +00:00
}
2019-11-17 16:30:43 +01:00
function normalize_snmp_ip_address ( $data )
{
// $data is received from snmpwalk, can be ipv4 xxx.xxx.xxx.xxx or ipv6 xx:xx:...:xx (16 chunks)
// ipv4 is returned unchanged, ipv6 is returned with one ':' removed out of two, like
// xxxx:xxxx:...:xxxx (8 chuncks)
2020-09-21 15:40:17 +02:00
return preg_replace ( '/([0-9a-fA-F]{2}):([0-9a-fA-F]{2})/' , '\1\2' , explode ( '%' , $data , 2 )[ 0 ]);
2019-11-17 16:30:43 +01:00
}
2016-08-28 12:32:58 -05:00
function guidv4 ( $data )
{
2015-04-10 17:00:32 +01:00
// http://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid#15875555
// From: Jack http://stackoverflow.com/users/1338292/ja%CD%A2ck
assert ( strlen ( $data ) == 16 );
$data [ 6 ] = chr ( ord ( $data [ 6 ]) & 0x0f | 0x40 ); // set version to 0100
$data [ 8 ] = chr ( ord ( $data [ 8 ]) & 0x3f | 0x80 ); // set bits 6-7 to 10
return vsprintf ( '%s%s-%s-%s-%s-%s%s%s' , str_split ( bin2hex ( $data ), 4 ));
}
2016-10-14 02:07:07 +01:00
/**
* @param $curl
*/
function set_curl_proxy ( $curl )
2016-08-28 12:32:58 -05:00
{
2017-03-22 10:17:13 +00:00
$proxy = get_proxy ();
2016-10-14 02:07:07 +01:00
2020-09-21 15:59:34 +02:00
$tmp = rtrim ( $proxy , '/' );
$proxy = str_replace ([ 'http://' , 'https://' ], '' , $tmp );
2020-09-21 15:40:17 +02:00
if ( ! empty ( $proxy )) {
2016-10-14 02:07:07 +01:00
curl_setopt ( $curl , CURLOPT_PROXY , $proxy );
2015-04-13 19:51:16 +10:00
}
}
2015-04-19 20:23:34 +01:00
2020-07-29 16:00:55 +02:00
/**
* Return the proxy url in guzzle format
*
* @return 'tcp://' + $proxy
*/
function get_guzzle_proxy ()
{
$proxy = get_proxy ();
2020-09-21 15:59:34 +02:00
$tmp = rtrim ( $proxy , '/' );
$proxy = str_replace ([ 'http://' , 'https://' ], '' , $tmp );
2021-03-28 17:25:30 -05:00
return empty ( $proxy ) ? '' : ( 'tcp://' . $proxy );
2020-07-29 16:00:55 +02:00
}
2017-03-22 10:17:13 +00:00
/**
* Return the proxy url
*
* @return array|bool|false|string
*/
function get_proxy ()
{
if ( getenv ( 'http_proxy' )) {
return getenv ( 'http_proxy' );
} elseif ( getenv ( 'https_proxy' )) {
return getenv ( 'https_proxy' );
2019-06-23 00:29:12 -05:00
} elseif ( $callback_proxy = Config :: get ( 'callback_proxy' )) {
return $callback_proxy ;
} elseif ( $http_proxy = Config :: get ( 'http_proxy' )) {
return $http_proxy ;
2017-03-22 10:17:13 +00:00
}
2020-09-21 15:40:17 +02:00
2017-03-22 10:17:13 +00:00
return false ;
}
2016-08-28 12:32:58 -05:00
function target_to_id ( $target )
{
2020-09-21 15:59:34 +02:00
if ( $target [ 0 ] . $target [ 1 ] == 'g:' ) {
$target = 'g' . dbFetchCell ( 'SELECT id FROM device_groups WHERE name = ?' , [ substr ( $target , 2 )]);
2016-08-28 12:32:58 -05:00
} else {
2020-09-21 15:40:17 +02:00
$target = dbFetchCell ( 'SELECT device_id FROM devices WHERE hostname = ?' , [ $target ]);
2015-04-19 20:23:34 +01:00
}
2020-09-21 15:40:17 +02:00
2015-04-19 20:23:34 +01:00
return $target ;
}
2016-08-28 12:32:58 -05:00
function fix_integer_value ( $value )
{
2015-06-02 17:36:10 +01:00
if ( $value < 0 ) {
2020-09-21 15:40:17 +02:00
$return = 4294967296 + $value ;
2016-08-28 12:32:58 -05:00
} else {
2015-06-02 17:36:10 +01:00
$return = $value ;
}
2020-09-21 15:40:17 +02:00
2015-06-02 17:36:10 +01:00
return $return ;
}
2015-06-11 19:34:11 +01:00
2019-01-19 12:46:03 -06:00
/**
* Find a device that has this IP. Checks ipv4_addresses and ipv6_addresses tables.
*
* @param string $ip
* @return \App\Models\Device|false
*/
function device_has_ip ( $ip )
2016-08-01 22:01:01 -05:00
{
2019-01-19 12:46:03 -06:00
if ( IPv6 :: isValid ( $ip )) {
$ip_address = \App\Models\Ipv6Address :: query ()
-> where ( 'ipv6_address' , IPv6 :: parse ( $ip , true ) -> uncompressed ())
-> with ( 'port.device' )
-> first ();
} elseif ( IPv4 :: isValid ( $ip )) {
$ip_address = \App\Models\Ipv4Address :: query ()
-> where ( 'ipv4_address' , $ip )
-> with ( 'port.device' )
-> first ();
2015-07-13 20:10:26 +02:00
}
2016-08-01 22:01:01 -05:00
2019-01-19 12:46:03 -06:00
if ( isset ( $ip_address ) && $ip_address -> port ) {
return $ip_address -> port -> device ;
}
return false ; // not an ipv4 or ipv6 address...
2015-06-11 19:34:11 +01:00
}
2015-06-22 21:55:31 +01:00
2015-09-25 19:03:58 +02:00
/**
* Try to determine the address family (IPv4 or IPv6) associated with an SNMP
* transport specifier (like "udp", "udp6", etc.).
*
* @param string $transport The SNMP transport specifier, for example "udp",
* "udp6", "tcp", or "tcp6". See `man snmpcmd`,
* section "Agent Specification" for a full list.
*
2018-07-13 17:08:00 -05:00
* @return string The address family associated with the given transport
* specifier: 'ipv4' (or local connections not associated
* with an IP stack) or 'ipv6'.
2015-09-25 19:03:58 +02:00
*/
2016-08-28 12:32:58 -05:00
function snmpTransportToAddressFamily ( $transport )
{
2018-07-13 17:08:00 -05:00
$ipv6_snmp_transport_specifiers = [ 'udp6' , 'udpv6' , 'udpipv6' , 'tcp6' , 'tcpv6' , 'tcpipv6' ];
2015-09-25 19:03:47 +02:00
if ( in_array ( $transport , $ipv6_snmp_transport_specifiers )) {
2018-07-13 17:08:00 -05:00
return 'ipv6' ;
2015-09-22 02:25:35 +02:00
}
2018-07-13 17:08:00 -05:00
return 'ipv4' ;
2015-09-22 02:25:35 +02:00
}
2015-10-22 21:10:33 +00:00
/**
* Checks if the $hostname provided exists in the DB already
*
* @param string $hostname The hostname to check for
2017-07-16 22:01:07 +01:00
* @param string $sysName The sysName to check
2015-10-22 21:10:33 +00:00
* @return bool true if hostname already exists
* false if hostname doesn't exist
2017-02-22 03:12:31 -06:00
*/
2017-07-16 22:01:07 +01:00
function host_exists ( $hostname , $sysName = null )
2016-08-28 12:32:58 -05:00
{
2020-09-21 15:59:34 +02:00
$query = 'SELECT COUNT(*) FROM `devices` WHERE `hostname`=?' ;
2020-09-21 15:40:17 +02:00
$params = [ $hostname ];
2017-07-16 22:01:07 +01:00
2020-09-21 15:40:17 +02:00
if ( ! empty ( $sysName ) && ! Config :: get ( 'allow_duplicate_sysName' )) {
2020-09-21 15:59:34 +02:00
$query .= ' OR `sysName`=?' ;
2017-07-16 22:01:07 +01:00
$params [] = $sysName ;
2020-09-21 15:40:17 +02:00
if ( ! empty ( Config :: get ( 'mydomain' ))) {
2019-06-23 00:29:12 -05:00
$full_sysname = rtrim ( $sysName , '.' ) . '.' . Config :: get ( 'mydomain' );
2020-09-21 15:59:34 +02:00
$query .= ' OR `sysName`=?' ;
2017-07-16 22:01:07 +01:00
$params [] = $full_sysname ;
2016-05-02 20:20:29 +00:00
}
2015-10-22 21:10:33 +00:00
}
2020-09-21 15:40:17 +02:00
2017-07-16 22:01:07 +01:00
return dbFetchCell ( $query , $params ) > 0 ;
2015-10-22 21:10:33 +00:00
}
2015-11-19 10:20:56 +00:00
2016-08-28 12:32:58 -05:00
function oxidized_reload_nodes ()
{
2019-06-23 00:29:12 -05:00
if ( Config :: get ( 'oxidized.enabled' ) === true && Config :: get ( 'oxidized.reload_nodes' ) === true && Config :: has ( 'oxidized.url' )) {
$oxidized_reload_url = Config :: get ( 'oxidized.url' ) . '/reload.json' ;
2016-01-17 17:47:26 +01:00
$ch = curl_init ( $oxidized_reload_url );
curl_setopt ( $ch , CURLOPT_TIMEOUT , 5 );
2017-02-03 14:12:42 +00:00
curl_setopt ( $ch , CURLOPT_TIMEOUT_MS , 5000 );
2016-01-17 17:47:26 +01:00
curl_setopt ( $ch , CURLOPT_CONNECTTIMEOUT , 5 );
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
curl_setopt ( $ch , CURLOPT_HEADER , 1 );
curl_exec ( $ch );
curl_close ( $ch );
}
}
2016-01-17 23:59:51 +00:00
/**
* Perform DNS lookup
*
* @param array $device Device array from database
* @param string $type The type of record to lookup
*
* @return string ip
*
2020-09-21 15:40:17 +02:00
**/
2016-08-28 12:32:58 -05:00
function dnslookup ( $device , $type = false , $return = false )
{
2016-12-30 00:13:12 +00:00
if ( filter_var ( $device [ 'hostname' ], FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 ) == true || filter_var ( $device [ 'hostname' ], FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 ) == true ) {
2017-01-28 00:02:39 +00:00
return false ;
2016-01-18 00:04:30 +00:00
}
2016-01-17 23:59:51 +00:00
if ( empty ( $type )) {
// We are going to use the transport to work out the record type
if ( $device [ 'transport' ] == 'udp6' || $device [ 'transport' ] == 'tcp6' ) {
$type = DNS_AAAA ;
$return = 'ipv6' ;
2016-08-28 12:32:58 -05:00
} else {
2016-01-17 23:59:51 +00:00
$type = DNS_A ;
$return = 'ip' ;
}
}
2016-01-18 12:54:25 +00:00
if ( empty ( $return )) {
2017-01-28 00:02:39 +00:00
return false ;
2016-01-18 12:54:25 +00:00
}
2016-08-28 12:32:58 -05:00
$record = dns_get_record ( $device [ 'hostname' ], $type );
2020-09-21 15:40:17 +02:00
2016-01-17 23:59:51 +00:00
return $record [ 0 ][ $return ];
} //end dnslookup
2016-02-01 23:29:37 +00:00
2017-09-03 13:58:39 -05:00
/**
* Create a new state index. Update translations if $states is given.
*
* For for backward compatibility:
* Returns null if $states is empty, $state_name already exists, and contains state translations
*
* @param string $state_name the unique name for this state translation
* @param array $states array of states, each must contain keys: descr, graph, value, generic
* @return int|null
*/
2020-09-21 15:40:17 +02:00
function create_state_index ( $state_name , $states = [])
2016-08-28 12:32:58 -05:00
{
2020-09-21 15:40:17 +02:00
$state_index_id = dbFetchCell ( 'SELECT `state_index_id` FROM state_indexes WHERE state_name = ? LIMIT 1' , [ $state_name ]);
if ( ! is_numeric ( $state_index_id )) {
$state_index_id = dbInsert ([ 'state_name' => $state_name ], 'state_indexes' );
2017-09-03 13:58:39 -05:00
// legacy code, return index so states are created
if ( empty ( $states )) {
return $state_index_id ;
}
}
// check or synchronize states
if ( empty ( $states )) {
2020-09-21 15:40:17 +02:00
$translations = dbFetchRows ( 'SELECT * FROM `state_translations` WHERE `state_index_id` = ?' , [ $state_index_id ]);
2017-04-29 16:58:06 +01:00
if ( count ( $translations ) == 0 ) {
2017-04-28 04:33:56 +01:00
// If we don't have any translations something has gone wrong so return the state_index_id so they get created.
return $state_index_id ;
}
2017-09-03 13:58:39 -05:00
} else {
sync_sensor_states ( $state_index_id , $states );
2016-02-27 16:23:58 +01:00
}
2017-09-03 13:58:39 -05:00
return null ;
}
/**
* Synchronize the sensor state translations with the database
*
* @param int $state_index_id index of the state
* @param array $states array of states, each must contain keys: descr, graph, value, generic
*/
function sync_sensor_states ( $state_index_id , $states )
{
$new_translations = array_reduce ( $states , function ( $array , $state ) use ( $state_index_id ) {
2020-09-21 15:40:17 +02:00
$array [ $state [ 'value' ]] = [
2017-09-03 13:58:39 -05:00
'state_index_id' => $state_index_id ,
'state_descr' => $state [ 'descr' ],
'state_draw_graph' => $state [ 'graph' ],
'state_value' => $state [ 'value' ],
2020-09-21 15:40:17 +02:00
'state_generic_value' => $state [ 'generic' ],
];
2017-09-03 13:58:39 -05:00
return $array ;
2020-09-21 15:40:17 +02:00
}, []);
2017-09-03 13:58:39 -05:00
$existing_translations = dbFetchRows (
'SELECT `state_index_id`,`state_descr`,`state_draw_graph`,`state_value`,`state_generic_value` FROM `state_translations` WHERE `state_index_id`=?' ,
2020-09-21 15:40:17 +02:00
[ $state_index_id ]
2017-09-03 13:58:39 -05:00
);
foreach ( $existing_translations as $translation ) {
$value = $translation [ 'state_value' ];
if ( isset ( $new_translations [ $value ])) {
if ( $new_translations [ $value ] != $translation ) {
dbUpdate (
$new_translations [ $value ],
'state_translations' ,
'`state_index_id`=? AND `state_value`=?' ,
2020-09-21 15:40:17 +02:00
[ $state_index_id , $value ]
2017-09-03 13:58:39 -05:00
);
}
// this translation is synchronized, it doesn't need to be inserted
unset ( $new_translations [ $value ]);
} else {
2020-09-21 15:40:17 +02:00
dbDelete ( 'state_translations' , '`state_index_id`=? AND `state_value`=?' , [ $state_index_id , $value ]);
2017-09-03 13:58:39 -05:00
}
}
// insert any new translations
dbBulkInsert ( $new_translations , 'state_translations' );
2016-02-26 23:40:34 +01:00
}
function create_sensor_to_state_index ( $device , $state_name , $index )
{
2020-09-21 15:40:17 +02:00
$sensor_entry = dbFetchRow ( 'SELECT sensor_id FROM `sensors` WHERE `sensor_class` = ? AND `device_id` = ? AND `sensor_type` = ? AND `sensor_index` = ?' , [
2016-02-26 23:40:34 +01:00
'state' ,
$device [ 'device_id' ],
$state_name ,
2020-09-21 15:40:17 +02:00
$index ,
]);
$state_indexes_entry = dbFetchRow ( 'SELECT state_index_id FROM `state_indexes` WHERE `state_name` = ?' , [
$state_name ,
]);
if ( ! empty ( $sensor_entry [ 'sensor_id' ]) && ! empty ( $state_indexes_entry [ 'state_index_id' ])) {
$insert = [
2016-02-26 23:40:34 +01:00
'sensor_id' => $sensor_entry [ 'sensor_id' ],
'state_index_id' => $state_indexes_entry [ 'state_index_id' ],
2020-09-21 15:40:17 +02:00
];
2016-08-28 12:32:58 -05:00
foreach ( $insert as $key => $val_check ) {
2020-09-21 15:40:17 +02:00
if ( ! isset ( $val_check )) {
2016-02-26 23:40:34 +01:00
unset ( $insert [ $key ]);
}
}
2016-02-26 23:56:54 +01:00
dbInsert ( $insert , 'sensors_to_state_indexes' );
2016-02-26 23:40:34 +01:00
}
}
2016-06-15 16:25:14 +00:00
2016-08-28 12:32:58 -05:00
function delta_to_bits ( $delta , $period )
{
2016-07-07 19:10:37 +01:00
return round (( $delta * 8 / $period ), 2 );
2016-06-15 16:25:14 +00:00
}
2016-07-07 19:11:13 +01:00
2016-08-28 12:32:58 -05:00
function report_this ( $message )
{
2019-06-23 00:29:12 -05:00
return '<h2>' . $message . ' Please <a href="' . Config :: get ( 'project_issues' ) . '">report this</a> to the ' . Config :: get ( 'project_name' ) . ' developers.</h2>' ;
2016-06-29 00:14:21 +01:00
} //end report_this()
2016-08-23 20:44:45 +02:00
2016-08-28 12:32:58 -05:00
function hytera_h2f ( $number , $nd )
2016-08-23 20:44:45 +02:00
{
2020-09-21 15:59:34 +02:00
if ( strlen ( str_replace ( ' ' , '' , $number )) == 4 ) {
2016-08-23 20:44:45 +02:00
$hex = '' ;
for ( $i = 0 ; $i < strlen ( $number ); $i ++ ) {
2020-03-21 15:49:15 -04:00
$byte = strtoupper ( dechex ( ord ( $number [ $i ])));
2020-09-21 15:40:17 +02:00
$byte = str_repeat ( '0' , 2 - strlen ( $byte )) . $byte ;
2020-09-21 15:59:34 +02:00
$hex .= $byte . ' ' ;
2016-08-23 20:44:45 +02:00
}
$number = $hex ;
unset ( $hex );
}
$r = '' ;
$y = explode ( ' ' , $number );
foreach ( $y as $z ) {
$r = $z . '' . $r ;
}
2020-09-21 15:40:17 +02:00
$hex = [];
2016-08-23 20:44:45 +02:00
$number = substr ( $r , 0 , - 1 );
//$number = str_replace(" ", "", $number);
2020-09-21 15:40:17 +02:00
for ( $i = 0 ; $i < strlen ( $number ); $i ++ ) {
$hex [] = substr ( $number , $i , 1 );
2016-08-23 20:44:45 +02:00
}
2020-09-21 15:40:17 +02:00
$dec = [];
2016-08-23 20:44:45 +02:00
$hexCount = count ( $hex );
2020-09-21 15:40:17 +02:00
for ( $i = 0 ; $i < $hexCount ; $i ++ ) {
$dec [] = hexdec ( $hex [ $i ]);
2016-08-23 20:44:45 +02:00
}
2020-09-21 15:59:34 +02:00
$binfinal = '' ;
2016-08-23 20:44:45 +02:00
$decCount = count ( $dec );
2020-09-21 15:40:17 +02:00
for ( $i = 0 ; $i < $decCount ; $i ++ ) {
2020-09-21 15:59:34 +02:00
$binfinal .= sprintf ( '%04d' , decbin ( $dec [ $i ]));
2016-08-23 20:44:45 +02:00
}
2020-09-21 15:40:17 +02:00
$sign = substr ( $binfinal , 0 , 1 );
$exp = substr ( $binfinal , 1 , 8 );
$exp = bindec ( $exp );
$exp -= 127 ;
$scibin = substr ( $binfinal , 9 );
$binint = substr ( $scibin , 0 , $exp );
$binpoint = substr ( $scibin , $exp );
2020-09-21 15:59:34 +02:00
$intnumber = bindec ( '1' . $binint );
2016-08-23 20:44:45 +02:00
2019-05-30 09:56:27 -05:00
$tmppoint = [];
2020-09-21 15:40:17 +02:00
for ( $i = 0 ; $i < strlen ( $binpoint ); $i ++ ) {
$tmppoint [] = substr ( $binpoint , $i , 1 );
2016-08-23 20:44:45 +02:00
}
2020-09-21 15:40:17 +02:00
$tmppoint = array_reverse ( $tmppoint );
$tpointnumber = number_format ( $tmppoint [ 0 ] / 2 , strlen ( $binpoint ), '.' , '' );
2016-08-23 20:44:45 +02:00
2020-09-21 15:59:34 +02:00
$pointnumber = '' ;
2020-09-21 15:40:17 +02:00
for ( $i = 1 ; $i < strlen ( $binpoint ); $i ++ ) {
$pointnumber = number_format ( $tpointnumber / 2 , strlen ( $binpoint ), '.' , '' );
$tpointnumber = $tmppoint [ $i + 1 ] . substr ( $pointnumber , 1 );
2016-08-23 20:44:45 +02:00
}
2020-09-21 15:40:17 +02:00
$floatfinal = $intnumber + $pointnumber ;
2016-08-23 20:44:45 +02:00
2020-09-21 15:40:17 +02:00
if ( $sign == 1 ) {
$floatfinal = - $floatfinal ;
2016-08-23 20:44:45 +02:00
}
2016-08-28 12:32:58 -05:00
return number_format ( $floatfinal , $nd , '.' , '' );
2016-08-23 20:44:45 +02:00
}
2016-10-06 10:40:37 +10:00
/*
* Cisco CIMC functions
*/
// Create an entry in the entPhysical table if it doesnt already exist.
function setCIMCentPhysical ( $location , $data , & $entphysical , & $index )
{
// Go get the location, this will create it if it doesnt exist.
$entPhysicalIndex = getCIMCentPhysical ( $location , $entphysical , $index );
// See if we need to update
2020-09-21 15:40:17 +02:00
$update = [];
2016-10-06 10:40:37 +10:00
foreach ( $data as $key => $value ) {
// Is the Array(DB) value different to the supplied data
if ( $entphysical [ $location ][ $key ] != $value ) {
$update [ $key ] = $value ;
$entphysical [ $location ][ $key ] = $value ;
} // End if
} // end foreach
// Do we need to update
if ( count ( $update ) > 0 ) {
2020-09-21 15:40:17 +02:00
dbUpdate ( $update , 'entPhysical' , '`entPhysical_id` = ?' , [ $entphysical [ $location ][ 'entPhysical_id' ]]);
2016-10-06 10:40:37 +10:00
}
$entPhysicalId = $entphysical [ $location ][ 'entPhysical_id' ];
2020-09-21 15:40:17 +02:00
return [ $entPhysicalId , $entPhysicalIndex ];
2016-10-06 10:40:37 +10:00
}
function getCIMCentPhysical ( $location , & $entphysical , & $index )
{
global $device ;
// Level 1 - Does the location exist
if ( isset ( $entphysical [ $location ])) {
// Yes, return the entPhysicalIndex.
return $entphysical [ $location ][ 'entPhysicalIndex' ];
} else {
/*
* No, the entry doesnt exist.
* Find its parent so we can create it.
*/
// Pull apart the location
$parts = explode ( '/' , $location );
// Level 2 - Are we at the root
if ( count ( $parts ) == 1 ) {
// Level 2 - Yes. We are the root, there is no parent
2020-09-21 15:59:34 +02:00
d_echo ( 'ROOT - ' . $location . " \n " );
2016-10-06 10:40:37 +10:00
$shortlocation = $location ;
$parent = 0 ;
} else {
// Level 2 - No. Need to go deeper.
2020-09-21 15:59:34 +02:00
d_echo ( 'NON-ROOT - ' . $location . " \n " );
2016-10-06 10:40:37 +10:00
$shortlocation = array_pop ( $parts );
$parentlocation = implode ( '/' , $parts );
2020-09-21 15:59:34 +02:00
d_echo ( 'Decend - parent location: ' . $parentlocation . " \n " );
2016-10-06 10:40:37 +10:00
$parent = getCIMCentPhysical ( $parentlocation , $entphysical , $index );
} // end if - Level 2
2020-09-21 15:59:34 +02:00
d_echo ( 'Parent: ' . $parent . " \n " );
2016-10-06 10:40:37 +10:00
// Now we have an ID, create the entry.
$index ++ ;
2020-09-21 15:40:17 +02:00
$insert = [
2016-10-06 10:40:37 +10:00
'device_id' => $device [ 'device_id' ],
'entPhysicalIndex' => $index ,
'entPhysicalClass' => 'container' ,
'entPhysicalVendorType' => $location ,
'entPhysicalName' => $shortlocation ,
'entPhysicalContainedIn' => $parent ,
'entPhysicalParentRelPos' => '-1' ,
2020-09-21 15:40:17 +02:00
];
2016-10-06 10:40:37 +10:00
// Add to the DB and Array.
$id = dbInsert ( $insert , 'entPhysical' );
2020-09-21 15:40:17 +02:00
$entphysical [ $location ] = dbFetchRow ( 'SELECT * FROM entPhysical WHERE entPhysical_id=?' , [ $id ]);
2016-10-06 10:40:37 +10:00
return $index ;
} // end if - Level 1
} // end function
2016-10-11 15:29:06 -06:00
2021-02-09 00:29:04 +01:00
/* idea from https://php.net/manual/en/function.hex2bin.php comments */
2016-10-11 15:29:06 -06:00
function hex2bin_compat ( $str )
{
if ( strlen ( $str ) % 2 !== 0 ) {
2020-09-21 15:40:17 +02:00
trigger_error ( __FUNCTION__ . '(): Hexadecimal input string must have an even length' , E_USER_WARNING );
2016-10-11 15:29:06 -06:00
}
2020-09-21 15:40:17 +02:00
2020-09-21 15:59:34 +02:00
return pack ( 'H*' , $str );
2016-10-11 15:29:06 -06:00
}
2020-09-21 15:40:17 +02:00
if ( ! function_exists ( 'hex2bin' )) {
2016-10-11 15:29:06 -06:00
// This is only a hack
function hex2bin ( $str )
{
return hex2bin_compat ( $str );
}
}
function q_bridge_bits2indices ( $hex_data )
{
/* convert hex string to an array of 1-based indices of the nonzero bits
* ie. '9a00' -> '100110100000' -> array(1, 4, 5, 7)
*/
$hex_data = str_replace ( ' ' , '' , $hex_data );
2021-03-30 15:26:01 -05:00
// we need an even number of digits for hex2bin
if ( strlen ( $hex_data ) % 2 === 1 ) {
$hex_data = '0' . $hex_data ;
}
2016-10-11 15:29:06 -06:00
$value = hex2bin ( $hex_data );
$length = strlen ( $value );
2020-09-21 15:40:17 +02:00
$indices = [];
2016-10-11 15:29:06 -06:00
for ( $i = 0 ; $i < $length ; $i ++ ) {
$byte = ord ( $value [ $i ]);
for ( $j = 7 ; $j >= 0 ; $j -- ) {
if ( $byte & ( 1 << $j )) {
2020-09-21 15:40:17 +02:00
$indices [] = 8 * $i + 8 - $j ;
2016-10-11 15:29:06 -06:00
}
}
}
2020-09-21 15:40:17 +02:00
2016-10-11 15:29:06 -06:00
return $indices ;
}
2016-10-14 18:14:18 -05:00
2016-11-23 00:57:19 -06:00
/**
* Intialize global stat arrays
*/
function initStats ()
{
2020-03-16 09:17:58 -05:00
global $snmp_stats , $snmp_stats_last ;
2016-11-23 00:57:19 -06:00
2020-09-21 15:40:17 +02:00
if ( ! isset ( $snmp_stats )) {
$snmp_stats = [
'ops' => [
2017-12-02 16:55:53 -06:00
'snmpget' => 0 ,
'snmpgetnext' => 0 ,
'snmpwalk' => 0 ,
2020-09-21 15:40:17 +02:00
],
'time' => [
2017-12-02 16:55:53 -06:00
'snmpget' => 0.0 ,
'snmpgetnext' => 0.0 ,
'snmpwalk' => 0.0 ,
2020-09-21 15:40:17 +02:00
],
];
2018-07-13 17:08:00 -05:00
$snmp_stats_last = $snmp_stats ;
2017-11-11 13:44:25 -06:00
}
2016-11-23 00:57:19 -06:00
}
2017-12-02 16:55:53 -06:00
/**
* Print out the stats totals since the last time this function was called
*
* @param bool $update_only Only update the stats checkpoint, don't print them
*/
function printChangedStats ( $update_only = false )
{
2020-03-16 09:17:58 -05:00
global $snmp_stats , $db_stats ;
global $snmp_stats_last , $db_stats_last ;
$output = sprintf (
2020-09-21 15:59:34 +02:00
'>> SNMP: [%d/%.2fs] MySQL: [%d/%.2fs]' ,
2021-03-21 12:50:40 +01:00
array_sum ( $snmp_stats [ 'ops' ] ?? []) - array_sum ( $snmp_stats_last [ 'ops' ] ?? []),
array_sum ( $snmp_stats [ 'time' ] ?? []) - array_sum ( $snmp_stats_last [ 'time' ] ?? []),
array_sum ( $db_stats [ 'ops' ] ?? []) - array_sum ( $db_stats_last [ 'ops' ] ?? []),
array_sum ( $db_stats [ 'time' ] ?? []) - array_sum ( $db_stats_last [ 'time' ] ?? [])
2020-03-16 09:17:58 -05:00
);
foreach ( app ( 'Datastore' ) -> getStats () as $datastore => $stats ) {
/** @var \LibreNMS\Data\Measure\MeasurementCollection $stats */
2020-09-21 15:59:34 +02:00
$output .= sprintf ( ' %s: [%d/%.2fs]' , $datastore , $stats -> getCountDiff (), $stats -> getDurationDiff ());
2020-03-16 09:17:58 -05:00
$stats -> checkpoint ();
}
2017-12-02 16:55:53 -06:00
2020-09-21 15:40:17 +02:00
if ( ! $update_only ) {
2020-03-16 09:17:58 -05:00
echo $output . PHP_EOL ;
2017-12-02 16:55:53 -06:00
}
// make a new checkpoint
$snmp_stats_last = $snmp_stats ;
$db_stats_last = $db_stats ;
}
2016-11-23 00:57:19 -06:00
/**
* Print global stat arrays
*/
function printStats ()
{
2020-03-16 09:17:58 -05:00
global $snmp_stats , $db_stats ;
2016-11-23 00:57:19 -06:00
2018-07-13 17:08:00 -05:00
if ( $snmp_stats ) {
printf (
"SNMP [%d/%.2fs]: Get[%d/%.2fs] Getnext[%d/%.2fs] Walk[%d/%.2fs] \n " ,
array_sum ( $snmp_stats [ 'ops' ]),
array_sum ( $snmp_stats [ 'time' ]),
$snmp_stats [ 'ops' ][ 'snmpget' ],
$snmp_stats [ 'time' ][ 'snmpget' ],
$snmp_stats [ 'ops' ][ 'snmpgetnext' ],
$snmp_stats [ 'time' ][ 'snmpgetnext' ],
$snmp_stats [ 'ops' ][ 'snmpwalk' ],
$snmp_stats [ 'time' ][ 'snmpwalk' ]
);
}
if ( $db_stats ) {
printf (
"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 " ,
array_sum ( $db_stats [ 'ops' ]),
array_sum ( $db_stats [ 'time' ]),
$db_stats [ 'ops' ][ 'fetchcell' ],
$db_stats [ 'time' ][ 'fetchcell' ],
$db_stats [ 'ops' ][ 'fetchrow' ],
$db_stats [ 'time' ][ 'fetchrow' ],
$db_stats [ 'ops' ][ 'fetchrows' ],
$db_stats [ 'time' ][ 'fetchrows' ],
$db_stats [ 'ops' ][ 'fetchcolumn' ],
$db_stats [ 'time' ][ 'fetchcolumn' ],
$db_stats [ 'ops' ][ 'update' ],
$db_stats [ 'time' ][ 'update' ],
$db_stats [ 'ops' ][ 'insert' ],
$db_stats [ 'time' ][ 'insert' ],
$db_stats [ 'ops' ][ 'delete' ],
$db_stats [ 'time' ][ 'delete' ]
);
}
2020-03-16 09:17:58 -05:00
foreach ( app ( 'Datastore' ) -> getStats () as $datastore => $stats ) {
/** @var \LibreNMS\Data\Measure\MeasurementCollection $stats */
2020-09-21 15:59:34 +02:00
printf ( '%s [%d/%.2fs]:' , $datastore , $stats -> getTotalCount (), $stats -> getTotalDuration ());
2020-03-11 07:52:52 -05:00
2020-03-16 09:17:58 -05:00
foreach ( $stats as $stat ) {
/** @var \LibreNMS\Data\Measure\MeasurementSummary $stat */
2020-09-21 15:59:34 +02:00
printf ( ' %s[%d/%.2fs]' , ucfirst ( $stat -> getType ()), $stat -> getCount (), $stat -> getDuration ());
2020-03-16 09:17:58 -05:00
}
echo PHP_EOL ;
}
2020-03-11 07:52:52 -05:00
}
2016-11-23 00:57:19 -06:00
/**
* @param string $stat snmpget, snmpwalk
* @param float $start_time The time the operation started with 'microtime(true)'
* @return float The calculated run time
*/
function recordSnmpStatistic ( $stat , $start_time )
{
global $snmp_stats ;
initStats ();
$runtime = microtime ( true ) - $start_time ;
2017-12-02 16:55:53 -06:00
$snmp_stats [ 'ops' ][ $stat ] ++ ;
$snmp_stats [ 'time' ][ $stat ] += $runtime ;
2020-09-21 15:40:17 +02:00
2016-11-23 00:57:19 -06:00
return $runtime ;
}
2017-02-14 12:56:16 +00:00
2018-11-19 15:58:59 +00:00
function runTraceroute ( $device )
{
$address_family = snmpTransportToAddressFamily ( $device [ 'transport' ]);
$trace_name = $address_family == 'ipv6' ? 'traceroute6' : 'traceroute' ;
$trace_path = Config :: get ( $trace_name , $trace_name );
$process = new Process ([ $trace_path , '-q' , '1' , '-w' , '1' , $device [ 'hostname' ]]);
$process -> run ();
if ( $process -> isSuccessful ()) {
return [ 'traceroute' => $process -> getOutput ()];
}
2020-09-21 15:40:17 +02:00
2018-11-19 15:58:59 +00:00
return [ 'output' => $process -> getErrorOutput ()];
}
2017-02-15 15:22:09 +00:00
/**
* @param $device
* @param bool $record_perf
* @return array
*/
function device_is_up ( $device , $record_perf = false )
{
$address_family = snmpTransportToAddressFamily ( $device [ 'transport' ]);
2020-01-30 13:20:30 +01:00
$poller_target = Device :: pollerTarget ( $device [ 'hostname' ]);
$ping_response = isPingable ( $poller_target , $address_family , $device [ 'attribs' ]);
2020-09-21 15:40:17 +02:00
$device_perf = $ping_response [ 'db' ];
2017-02-15 15:22:09 +00:00
$device_perf [ 'device_id' ] = $device [ 'device_id' ];
2020-09-21 15:40:17 +02:00
$device_perf [ 'timestamp' ] = [ 'NOW()' ];
2020-10-28 03:05:19 +02:00
$maintenance = DeviceCache :: get ( $device [ 'device_id' ]) -> isUnderMaintenance ();
$consider_maintenance = Config :: get ( 'graphing.availability_consider_maintenance' );
$state_update_again = false ;
2017-02-15 15:22:09 +00:00
2018-11-19 15:58:59 +00:00
if ( $record_perf === true && can_ping_device ( $device [ 'attribs' ])) {
$trace_debug = [];
if ( $ping_response [ 'result' ] === false && Config :: get ( 'debug.run_trace' , false )) {
$trace_debug = runTraceroute ( $device );
}
$device_perf [ 'debug' ] = json_encode ( $trace_debug );
2017-02-15 15:22:09 +00:00
dbInsert ( $device_perf , 'device_perf' );
2020-02-09 09:19:30 -05:00
// if device_perf is inserted and the ping was successful then update device last_ping timestamp
2020-09-21 15:59:34 +02:00
if ( ! empty ( $ping_response [ 'last_ping_timetaken' ]) && $ping_response [ 'last_ping_timetaken' ] != '0' ) {
2020-02-09 09:19:30 -05:00
dbUpdate (
2020-09-21 15:40:17 +02:00
[ 'last_ping' => NOW (), 'last_ping_timetaken' => $ping_response [ 'last_ping_timetaken' ]],
2020-02-09 09:19:30 -05:00
'devices' ,
'device_id=?' ,
2020-09-21 15:40:17 +02:00
[ $device [ 'device_id' ]]
2020-02-09 09:19:30 -05:00
);
}
2017-02-15 15:22:09 +00:00
}
2020-09-21 15:40:17 +02:00
$response = [];
2017-02-15 15:22:09 +00:00
$response [ 'ping_time' ] = $ping_response [ 'last_ping_timetaken' ];
if ( $ping_response [ 'result' ]) {
2017-10-28 05:59:25 +02:00
if ( $device [ 'snmp_disable' ] || isSNMPable ( $device )) {
2020-09-21 15:40:17 +02:00
$response [ 'status' ] = '1' ;
2017-02-15 15:22:09 +00:00
$response [ 'status_reason' ] = '' ;
} else {
echo 'SNMP Unreachable' ;
2020-09-21 15:40:17 +02:00
$response [ 'status' ] = '0' ;
2017-02-15 15:22:09 +00:00
$response [ 'status_reason' ] = 'snmp' ;
}
} else {
echo 'Unpingable' ;
2020-09-21 15:40:17 +02:00
$response [ 'status' ] = '0' ;
2017-02-15 15:22:09 +00:00
$response [ 'status_reason' ] = 'icmp' ;
}
2020-10-28 03:05:19 +02:00
// Special case where the device is still down, optional mode is on, device not in maintenance mode and has no ongoing outages
if (( $consider_maintenance && ! $maintenance ) && ( $device [ 'status' ] == '0' && $response [ 'status' ] == '0' )) {
$state_update_again = empty ( dbFetchCell ( 'SELECT going_down FROM device_outages WHERE device_id=? AND up_again IS NULL ORDER BY going_down DESC' , [ $device [ 'device_id' ]]));
}
2017-07-27 08:27:52 -05:00
2020-10-28 03:05:19 +02:00
if ( $device [ 'status' ] != $response [ 'status' ] || $device [ 'status_reason' ] != $response [ 'status_reason' ] || $state_update_again ) {
if ( ! $state_update_again ) {
dbUpdate (
[ 'status' => $response [ 'status' ], 'status_reason' => $response [ 'status_reason' ]],
'devices' ,
'device_id=?' ,
[ $device [ 'device_id' ]]
);
}
2020-08-28 13:01:54 +02:00
2017-07-27 08:27:52 -05:00
if ( $response [ 'status' ]) {
$type = 'up' ;
$reason = $device [ 'status_reason' ];
2020-06-22 22:57:30 +02:00
2020-10-12 00:55:06 +03:00
$going_down = dbFetchCell ( 'SELECT going_down FROM device_outages WHERE device_id=? AND up_again IS NULL ORDER BY going_down DESC' , [ $device [ 'device_id' ]]);
2020-09-21 15:40:17 +02:00
if ( ! empty ( $going_down )) {
2020-10-28 03:05:19 +02:00
$up_again = time ();
2020-08-28 13:01:54 +02:00
dbUpdate (
2020-09-21 15:40:17 +02:00
[ 'device_id' => $device [ 'device_id' ], 'up_again' => $up_again ],
2020-08-28 13:01:54 +02:00
'device_outages' ,
2020-10-12 00:55:06 +03:00
'device_id=? and going_down=? and up_again is NULL' ,
[ $device [ 'device_id' ], $going_down ]
2020-08-28 13:01:54 +02:00
);
2020-06-22 22:57:30 +02:00
}
2017-07-27 08:27:52 -05:00
} else {
$type = 'down' ;
$reason = $response [ 'status_reason' ];
2020-08-28 13:01:54 +02:00
2020-11-09 05:15:29 +01:00
if ( $device [ 'status' ] != $response [ 'status' ]) {
if ( ! $consider_maintenance || ( ! $maintenance && $consider_maintenance )) {
// use current time as a starting point when an outage starts
$data = [ 'device_id' => $device [ 'device_id' ],
'going_down' => time (), ];
dbInsert ( $data , 'device_outages' );
}
2020-10-28 03:05:19 +02:00
}
2017-07-27 08:27:52 -05:00
}
log_event ( 'Device status changed to ' . ucfirst ( $type ) . " from $reason check." , $device , $type );
2017-02-15 15:22:09 +00:00
}
2020-09-21 15:40:17 +02:00
2017-02-15 15:22:09 +00:00
return $response ;
}
2017-02-14 17:04:55 -06:00
function update_device_logo ( & $device )
2017-02-14 12:56:16 +00:00
{
$icon = getImageName ( $device , false );
if ( $icon != $device [ 'icon' ]) {
log_event ( 'Device Icon changed ' . $device [ 'icon' ] . " => $icon " , $device , 'system' , 3 );
$device [ 'icon' ] = $icon ;
2020-09-21 15:40:17 +02:00
dbUpdate ([ 'icon' => $icon ], 'devices' , 'device_id=?' , [ $device [ 'device_id' ]]);
2017-02-14 12:56:16 +00:00
echo "Changed Icon! : $icon \n " ;
}
}
2017-03-22 10:17:13 +00:00
/**
* Function to generate PeeringDB Cache
*/
function cache_peeringdb ()
{
2019-06-23 00:29:12 -05:00
if ( Config :: get ( 'peeringdb.enabled' ) === true ) {
2017-03-22 10:17:13 +00:00
$peeringdb_url = 'https://peeringdb.com/api' ;
// We cache for 71 hours
2020-09-21 15:59:34 +02:00
$cached = dbFetchCell ( 'SELECT count(*) FROM `pdb_ix` WHERE (UNIX_TIMESTAMP() - timestamp) < 255600' );
2017-03-22 10:17:13 +00:00
if ( $cached == 0 ) {
2017-12-09 17:19:56 +00:00
$rand = rand ( 3 , 30 );
2017-03-22 10:17:13 +00:00
echo "No cached PeeringDB data found, sleeping for $rand seconds" . PHP_EOL ;
sleep ( $rand );
2018-08-26 07:42:21 -05:00
$peer_keep = [];
$ix_keep = [];
2020-09-21 15:59:34 +02:00
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 ) {
2017-03-22 10:17:13 +00:00
$asn = $as [ 'bgpLocalAs' ];
2020-09-21 15:40:17 +02:00
$get = Requests :: get ( $peeringdb_url . '/net?depth=2&asn=' . $asn , [], [ 'proxy' => get_proxy ()]);
2017-03-22 10:17:13 +00:00
$json_data = $get -> body ;
$data = json_decode ( $json_data );
2020-03-21 15:49:15 -04:00
$ixs = $data -> { 'data' }[ 0 ] -> { 'netixlan_set' };
2017-03-22 10:17:13 +00:00
foreach ( $ixs as $ix ) {
$ixid = $ix -> { 'ix_id' };
2020-09-21 15:59:34 +02:00
$tmp_ix = dbFetchRow ( 'SELECT * FROM `pdb_ix` WHERE `ix_id` = ? AND asn = ?' , [ $ixid , $asn ]);
2017-03-22 10:17:13 +00:00
if ( $tmp_ix ) {
$pdb_ix_id = $tmp_ix [ 'pdb_ix_id' ];
2020-09-21 15:40:17 +02:00
$update = [ 'name' => $ix -> { 'name' }, 'timestamp' => time ()];
dbUpdate ( $update , 'pdb_ix' , '`ix_id` = ? AND `asn` = ?' , [ $ixid , $asn ]);
2017-03-22 10:17:13 +00:00
} else {
2020-09-21 15:40:17 +02:00
$insert = [
2017-03-22 10:17:13 +00:00
'ix_id' => $ixid ,
'name' => $ix -> { 'name' },
'asn' => $asn ,
2020-09-21 15:40:17 +02:00
'timestamp' => time (),
];
2017-03-22 10:17:13 +00:00
$pdb_ix_id = dbInsert ( $insert , 'pdb_ix' );
}
2018-08-26 07:42:21 -05:00
$ix_keep [] = $pdb_ix_id ;
2020-09-21 15:40:17 +02:00
$get_ix = Requests :: get ( " $peeringdb_url /netixlan?ix_id= $ixid " , [], [ 'proxy' => get_proxy ()]);
2017-03-22 10:17:13 +00:00
$ix_json = $get_ix -> body ;
$ix_data = json_decode ( $ix_json );
$peers = $ix_data -> { 'data' };
foreach ( $peers as $index => $peer ) {
$peer_name = get_astext ( $peer -> { 'asn' });
2020-09-21 15:59:34 +02:00
$tmp_peer = dbFetchRow ( 'SELECT * FROM `pdb_ix_peers` WHERE `peer_id` = ? AND `ix_id` = ?' , [ $peer -> { 'id' }, $ixid ]);
2017-03-22 10:17:13 +00:00
if ( $tmp_peer ) {
$peer_keep [] = $tmp_peer [ 'pdb_ix_peers_id' ];
2020-09-21 15:40:17 +02:00
$update = [
2017-03-22 10:17:13 +00:00
'remote_asn' => $peer -> { 'asn' },
'remote_ipaddr4' => $peer -> { 'ipaddr4' },
'remote_ipaddr6' => $peer -> { 'ipaddr6' },
'name' => $peer_name ,
2020-09-21 15:40:17 +02:00
];
dbUpdate ( $update , 'pdb_ix_peers' , '`pdb_ix_peers_id` = ?' , [ $tmp_peer [ 'pdb_ix_peers_id' ]]);
2017-03-22 10:17:13 +00:00
} else {
2020-09-21 15:40:17 +02:00
$peer_insert = [
2017-03-22 10:17:13 +00:00
'ix_id' => $ixid ,
'peer_id' => $peer -> { 'id' },
'remote_asn' => $peer -> { 'asn' },
'remote_ipaddr4' => $peer -> { 'ipaddr4' },
'remote_ipaddr6' => $peer -> { 'ipaddr6' },
'name' => $peer_name ,
2020-09-21 15:40:17 +02:00
'timestamp' => time (),
];
2017-03-22 10:17:13 +00:00
$peer_keep [] = dbInsert ( $peer_insert , 'pdb_ix_peers' );
}
}
}
2018-08-26 07:42:21 -05:00
}
// cleanup
if ( empty ( $peer_keep )) {
dbDelete ( 'pdb_ix_peers' );
} else {
2020-09-21 15:59:34 +02:00
dbDelete ( 'pdb_ix_peers' , '`pdb_ix_peers_id` NOT IN ' . dbGenPlaceholders ( count ( $peer_keep )), $peer_keep );
2018-08-26 07:42:21 -05:00
}
if ( empty ( $ix_keep )) {
dbDelete ( 'pdb_ix' );
} else {
2020-09-21 15:59:34 +02:00
dbDelete ( 'pdb_ix' , '`pdb_ix_id` NOT IN ' . dbGenPlaceholders ( count ( $ix_keep )), $ix_keep );
2017-03-22 10:17:13 +00:00
}
} else {
2020-09-21 15:59:34 +02:00
echo 'Cached PeeringDB data found.....' . PHP_EOL ;
2017-03-22 10:17:13 +00:00
}
} else {
echo 'Peering DB integration disabled' . PHP_EOL ;
}
}
2017-04-05 09:00:28 +01:00
2017-04-13 04:18:12 -05:00
/**
* Get an array of the schema files.
* schema_version => full_file_name
*
* @return mixed
*/
function get_schema_list ()
{
// glob returns an array sorted by filename
2019-06-23 00:29:12 -05:00
$files = glob ( Config :: get ( 'install_dir' ) . '/sql-schema/*.sql' );
2017-04-13 04:18:12 -05:00
// set the keys to the db schema version
2019-01-17 08:53:32 -06:00
$files = array_reduce ( $files , function ( $array , $file ) {
2020-09-21 15:40:17 +02:00
$array [( int ) basename ( $file , '.sql' )] = $file ;
2017-04-13 04:18:12 -05:00
return $array ;
2019-01-17 08:53:32 -06:00
}, []);
ksort ( $files ); // fix dbSchema 1000 order
2020-09-21 15:40:17 +02:00
2019-01-17 08:53:32 -06:00
return $files ;
2017-04-13 04:18:12 -05:00
}
/**
* Get the current database schema, will return 0 if there is no schema.
*
* @return int
*/
function get_db_schema ()
{
2018-08-17 15:29:20 -05:00
try {
2018-11-28 16:49:18 -06:00
$db = \LibreNMS\DB\Eloquent :: DB ();
if ( $db ) {
2020-09-21 15:40:17 +02:00
return ( int ) $db -> table ( 'dbSchema' )
2018-11-28 16:49:18 -06:00
-> orderBy ( 'version' , 'DESC' )
-> value ( 'version' );
}
2018-08-17 15:29:20 -05:00
} catch ( PDOException $e ) {
2018-11-28 16:49:18 -06:00
// return default
2018-08-17 15:29:20 -05:00
}
2018-11-28 16:49:18 -06:00
return 0 ;
2017-04-13 04:18:12 -05:00
}
2017-05-01 23:49:11 -05:00
/**
* @param $device
* @return int|null
*/
function get_device_oid_limit ( $device )
{
2018-11-21 17:25:39 -02:00
// device takes priority
2021-02-14 20:36:55 -06:00
if ( ! empty ( $device [ 'attribs' ][ 'snmp_max_oid' ])) {
2019-11-14 21:56:06 +00:00
return $device [ 'attribs' ][ 'snmp_max_oid' ];
2018-11-21 17:25:39 -02:00
}
2017-05-01 23:49:11 -05:00
2018-11-21 17:25:39 -02:00
// then os
$os_max = Config :: getOsSetting ( $device [ 'os' ], 'snmp_max_oid' , 0 );
if ( $os_max > 0 ) {
return $os_max ;
2017-05-01 23:49:11 -05:00
}
2018-11-21 17:25:39 -02:00
// then global
$global_max = Config :: get ( 'snmp.max_oid' , 10 );
2020-09-21 15:40:17 +02:00
2018-11-21 17:25:39 -02:00
return $global_max > 0 ? $global_max : 10 ;
2017-05-01 23:49:11 -05:00
}
2017-10-17 11:36:49 -05:00
2017-11-24 03:37:52 -06:00
/**
* If Distributed, create a lock, then purge the mysql table
*
* @param string $table
* @param string $sql
* @return int exit code
*/
function lock_and_purge ( $table , $sql )
{
2020-10-07 07:36:35 -05:00
$purge_name = $table . '_purge' ;
$lock = Cache :: lock ( $purge_name , 86000 );
if ( $lock -> get ()) {
2017-11-24 03:37:52 -06:00
$purge_days = Config :: get ( $purge_name );
$name = str_replace ( '_' , ' ' , ucfirst ( $table ));
if ( is_numeric ( $purge_days )) {
2020-09-21 15:40:17 +02:00
if ( dbDelete ( $table , $sql , [ $purge_days ])) {
2017-11-24 03:37:52 -06:00
echo " $name cleared for entries over $purge_days days \n " ;
}
}
2020-10-07 07:36:35 -05:00
$lock -> release ();
2020-09-21 15:40:17 +02:00
2017-11-24 03:37:52 -06:00
return 0 ;
}
2020-10-07 07:36:35 -05:00
return - 1 ;
2017-11-24 03:37:52 -06:00
}
2018-04-22 21:01:37 +08:00
2019-11-05 14:49:09 +01:00
/**
* If Distributed, create a lock, then purge the mysql table according to the sql query
*
* @param string $table
* @param string $sql
* @param string $msg
* @return int exit code
*/
function lock_and_purge_query ( $table , $sql , $msg )
{
$purge_name = $table . '_purge' ;
$purge_duration = Config :: get ( $purge_name );
2020-09-21 15:40:17 +02:00
if ( ! ( is_numeric ( $purge_duration ) && $purge_duration > 0 )) {
2019-11-05 14:49:09 +01:00
return - 2 ;
}
2020-10-07 07:36:35 -05:00
$lock = Cache :: lock ( $purge_name , 86000 );
if ( $lock -> get ()) {
2020-09-21 15:40:17 +02:00
if ( dbQuery ( $sql , [ $purge_duration ])) {
2019-11-05 14:49:09 +01:00
printf ( $msg , $purge_duration );
}
2020-10-07 07:36:35 -05:00
$lock -> release ();
2020-09-21 15:40:17 +02:00
2020-10-07 07:36:35 -05:00
return 0 ;
2019-11-05 14:49:09 +01:00
}
2020-09-21 15:40:17 +02:00
2020-10-07 07:36:35 -05:00
return - 1 ;
2019-11-05 14:49:09 +01:00
}
2018-06-05 09:28:13 +00:00
/**
* Check if disk is valid to poll.
* Settings: bad_disk_regexp
*
* @param array $disk
* @param array $device
* @return bool
*/
function is_disk_valid ( $disk , $device )
{
foreach ( Config :: getCombined ( $device [ 'os' ], 'bad_disk_regexp' ) as $bir ) {
2020-09-21 15:59:34 +02:00
if ( preg_match ( $bir . 'i' , $disk [ 'diskIODevice' ])) {
2018-06-05 09:28:13 +00:00
d_echo ( "Ignored Disk: { $disk [ 'diskIODevice' ] } (matched: $bir ) \n " );
2020-09-21 15:40:17 +02:00
2018-06-05 09:28:13 +00:00
return false ;
}
}
2020-09-21 15:40:17 +02:00
2018-06-05 09:28:13 +00:00
return true ;
}
2018-09-05 02:09:27 +02:00
/**
* Queues a hostname to be refreshed by Oxidized
* Settings: oxidized.url
*
* @param string $hostname
* @param string $msg
* @param string $username
* @return bool
*/
function oxidized_node_update ( $hostname , $msg , $username = 'not_provided' )
{
// Work around https://github.com/rack/rack/issues/337
2020-09-21 15:59:34 +02:00
$msg = str_replace ( '%' , '' , $msg );
$postdata = [ 'user' => $username , 'msg' => $msg ];
2018-09-05 02:09:27 +02:00
$oxidized_url = Config :: get ( 'oxidized.url' );
2020-09-21 15:40:17 +02:00
if ( ! empty ( $oxidized_url )) {
2018-09-05 02:09:27 +02:00
Requests :: put ( " $oxidized_url /node/next/ $hostname " , [], json_encode ( $postdata ), [ 'proxy' => get_proxy ()]);
2020-09-21 15:40:17 +02:00
2018-09-05 02:09:27 +02:00
return true ;
}
2020-09-21 15:40:17 +02:00
2018-09-05 02:09:27 +02:00
return false ;
} //end oxidized_node_update()
2020-04-25 05:47:20 +02:00
/**
* @params int code
* @params int subcode
* @return string
* Take a BGP error code and subcode to return a string representation of it
*/
function describe_bgp_error_code ( $code , $subcode )
{
// https://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#bgp-parameters-3
2020-09-21 15:59:34 +02:00
$message = 'Unknown' ;
2020-04-25 05:47:20 +02:00
2020-09-21 15:59:34 +02:00
$error_code_key = 'bgp.error_codes.' . $code ;
$error_subcode_key = 'bgp.error_subcodes.' . $code . '.' . $subcode ;
2020-04-25 05:47:20 +02:00
$error_code_message = __ ( $error_code_key );
$error_subcode_message = __ ( $error_subcode_key );
if ( $error_subcode_message != $error_subcode_key ) {
2020-09-21 15:59:34 +02:00
$message = $error_code_message . ' - ' . $error_subcode_message ;
2020-04-25 05:47:20 +02:00
} elseif ( $error_code_message != $error_code_key ) {
$message = $error_code_message ;
}
return $message ;
}