2012-05-16 16:26:19 +00:00
< ? php
2014-12-15 11:10:26 +00:00
/* Copyright ( C ) 2014 Daniel Preussker < f0o @ devilcode . org >
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
2015-07-13 20:10:26 +02:00
*
2014-12-15 11:10:26 +00:00
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2015-07-13 20:10:26 +02:00
*
2014-12-15 11:10:26 +00:00
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http :// www . gnu . org / licenses />. */
2012-05-16 16:26:19 +00:00
2015-07-13 20:10:26 +02:00
/*
2014-12-15 11:10:26 +00:00
* Alerts Tracking
* @ author Daniel Preussker < f0o @ devilcode . org >
* @ copyright 2014 f0o , LibreNMS
* @ license GPL
* @ package LibreNMS
* @ subpackage Alerts
2012-05-16 16:26:19 +00:00
*/
2017-11-18 11:33:03 +01:00
use LibreNMS\Authentication\Auth ;
2015-04-03 18:22:29 +00:00
2012-05-16 16:26:19 +00:00
/**
2014-12-15 11:10:26 +00:00
* Generate SQL from Rule
* @ param string $rule Rule to generate SQL for
2015-04-25 11:10:37 +00:00
* @ return string | boolean
2014-12-15 11:10:26 +00:00
*/
2016-08-28 12:32:58 -05:00
function GenSQL ( $rule )
{
2015-07-13 20:10:26 +02:00
$rule = RunMacros ( $rule );
2016-08-28 12:32:58 -05:00
if ( empty ( $rule )) {
2015-07-13 20:10:26 +02:00
//Cannot resolve Macros due to recursion. Rule is invalid.
return false ;
}
//Pretty-print rule to dissect easier
2017-05-13 23:32:41 +02:00
$pretty = array ( '&&' => ' && ' , '||' => ' || ' );
2016-08-28 12:32:58 -05:00
$rule = str_replace ( array_keys ( $pretty ), $pretty , $rule );
$tmp = explode ( " " , $rule );
2015-07-13 20:10:26 +02:00
$tables = array ();
2016-08-28 12:32:58 -05:00
foreach ( $tmp as $opt ) {
if ( strstr ( $opt , '%' ) && strstr ( $opt , '.' )) {
$tmpp = explode ( " . " , $opt , 2 );
$tmpp [ 0 ] = str_replace ( " % " , " " , $tmpp [ 0 ]);
$tables [] = mres ( str_replace ( " ( " , " " , $tmpp [ 0 ]));
$rule = str_replace ( $opt , $tmpp [ 0 ] . '.' . $tmpp [ 1 ], $rule );
2015-07-13 20:10:26 +02:00
}
}
$tables = array_keys ( array_flip ( $tables ));
2016-08-28 12:32:58 -05:00
if ( dbFetchCell ( 'SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME = ?' , array ( $tables [ 0 ], 'device_id' )) != 1 ) {
2015-07-13 20:10:26 +02:00
//Our first table has no valid glue, append the 'devices' table to it!
array_unshift ( $tables , 'devices' );
}
$x = sizeof ( $tables ) - 1 ;
$i = 0 ;
$join = " " ;
2016-08-28 12:32:58 -05:00
while ( $i < $x ) {
if ( isset ( $tables [ $i + 1 ])) {
$gtmp = ResolveGlues ( array ( $tables [ $i + 1 ]), 'device_id' );
if ( $gtmp === false ) {
2015-11-30 19:13:32 +00:00
//Cannot resolve glue-chain. Rule is invalid.
return false ;
2015-07-13 20:10:26 +02:00
}
2015-11-30 19:13:32 +00:00
$last = " " ;
$qry = " " ;
2016-08-28 12:32:58 -05:00
foreach ( $gtmp as $glue ) {
if ( empty ( $last )) {
list ( $tmp , $last ) = explode ( '.' , $glue );
2015-11-30 19:13:32 +00:00
$qry .= $glue . ' = ' ;
2016-08-28 12:32:58 -05:00
} else {
list ( $tmp , $new ) = explode ( '.' , $glue );
2015-11-30 19:13:32 +00:00
$qry .= $tmp . '.' . $last . ' && ' . $tmp . '.' . $new . ' = ' ;
$last = $new ;
}
2016-08-28 12:32:58 -05:00
if ( ! in_array ( $tmp , $tables )) {
2015-11-30 19:13:32 +00:00
$tables [] = $tmp ;
2015-07-13 20:10:26 +02:00
}
}
2015-11-30 19:13:32 +00:00
$join .= " ( " . $qry . $tables [ 0 ] . " .device_id ) && " ;
2015-07-13 20:10:26 +02:00
}
$i ++ ;
}
2016-08-28 12:32:58 -05:00
$sql = " SELECT * FROM " . implode ( " , " , $tables ) . " WHERE ( " . $join . " " . str_replace ( " ( " , " " , $tables [ 0 ]) . " .device_id = ?) && ( " . str_replace ( array ( " % " , " @ " , " !~ " , " ~ " ), array ( " " , " .* " , " NOT REGEXP " , " REGEXP " ), $rule ) . " ) " ;
2015-07-13 20:10:26 +02:00
return $sql ;
2012-05-16 16:26:19 +00:00
}
2015-04-24 19:05:55 +00:00
/**
* Process Macros
* @ param string $rule Rule to process
2015-05-07 14:09:41 +00:00
* @ param int $x Recursion - Anchor
2015-04-25 11:10:37 +00:00
* @ return string | boolean
2015-04-24 19:05:55 +00:00
*/
2016-08-28 12:32:58 -05:00
function RunMacros ( $rule , $x = 1 )
{
2015-07-13 20:10:26 +02:00
global $config ;
krsort ( $config [ 'alert' ][ 'macros' ][ 'rule' ]);
2016-08-28 12:32:58 -05:00
foreach ( $config [ 'alert' ][ 'macros' ][ 'rule' ] as $macro => $value ) {
if ( ! strstr ( $macro , " " )) {
$rule = str_replace ( '%macros.' . $macro , '(' . $value . ')' , $rule );
2015-07-13 20:10:26 +02:00
}
}
2016-08-28 12:32:58 -05:00
if ( strstr ( $rule , " %macros " )) {
if ( ++ $x < 30 ) {
$rule = RunMacros ( $rule , $x );
} else {
2015-07-13 20:10:26 +02:00
return false ;
}
}
return $rule ;
2015-04-24 19:05:55 +00:00
}
2015-04-03 18:22:29 +00:00
/**
* Get Alert - Rules for Devices
* @ param int $device Device - ID
* @ return array
*/
2016-08-28 12:32:58 -05:00
function GetRules ( $device )
{
2015-07-13 20:10:26 +02:00
$groups = GetGroupsFromDevice ( $device );
$params = array ( $device , $device );
$where = " " ;
2016-08-28 12:32:58 -05:00
foreach ( $groups as $group ) {
2015-07-13 20:10:26 +02:00
$where .= " || alert_map.target = ? " ;
$params [] = 'g' . $group ;
}
2016-08-28 12:32:58 -05:00
return dbFetchRows ( 'SELECT alert_rules.* FROM alert_rules LEFT JOIN alert_map ON alert_rules.id=alert_map.rule WHERE alert_rules.disabled = 0 && ( (alert_rules.device_id = -1 || alert_rules.device_id = ? ) || alert_map.target = ? ' . $where . ' )' , $params );
2015-04-03 18:22:29 +00:00
}
2012-05-16 16:26:19 +00:00
2015-04-20 16:39:55 +00:00
/**
* Check if device is under maintenance
* @ param int $device Device - ID
2015-04-20 18:18:22 +00:00
* @ return int
2015-04-20 16:39:55 +00:00
*/
2016-08-28 12:32:58 -05:00
function IsMaintenance ( $device )
{
2015-07-13 20:10:26 +02:00
$groups = GetGroupsFromDevice ( $device );
$params = array ( $device );
$where = " " ;
2016-08-28 12:32:58 -05:00
foreach ( $groups as $group ) {
2015-07-13 20:10:26 +02:00
$where .= " || alert_schedule_items.target = ? " ;
$params [] = 'g' . $group ;
}
2017-01-22 19:00:15 +01:00
return dbFetchCell ( 'SELECT alert_schedule.schedule_id FROM alert_schedule LEFT JOIN alert_schedule_items ON alert_schedule.schedule_id=alert_schedule_items.schedule_id WHERE ( alert_schedule_items.target = ?' . $where . ' ) && ((alert_schedule.recurring = 0 AND (NOW() BETWEEN alert_schedule.start AND alert_schedule.end)) OR (alert_schedule.recurring = 1 AND (alert_schedule.start_recurring_dt <= date_format(NOW(), \'%Y-%m-%d\') AND (end_recurring_dt >= date_format(NOW(), \'%Y-%m-%d\') OR end_recurring_dt is NULL OR end_recurring_dt = \'0000-00-00\' OR end_recurring_dt = \'\')) AND (date_format(now(), \'%H:%i:%s\') BETWEEN `start_recurring_hr` AND end_recurring_hr) AND (recurring_day LIKE CONCAT(\'%\',date_format(now(), \'%w\'),\'%\') OR recurring_day is null or recurring_day = \'\'))) LIMIT 1' , $params );
2015-04-20 16:39:55 +00:00
}
2012-05-16 16:26:19 +00:00
/**
2014-12-15 11:10:26 +00:00
* Run all rules for a device
* @ param int $device Device - ID
* @ return void
*/
2016-08-28 12:32:58 -05:00
function RunRules ( $device )
{
if ( IsMaintenance ( $device ) > 0 ) {
2015-07-13 20:10:26 +02:00
echo " Under Maintenance, Skipping alerts. \r \n " ;
return false ;
}
2016-08-28 12:32:58 -05:00
foreach ( GetRules ( $device ) as $rule ) {
2016-09-25 16:01:58 +01:00
c_echo ( 'Rule %p#' . $rule [ 'id' ] . ' (' . $rule [ 'name' ] . '):%n ' );
2017-03-23 08:23:01 +00:00
$extra = json_decode ( $rule [ 'extra' ], true );
if ( isset ( $extra [ 'invert' ])) {
$inv = ( bool ) $extra [ 'invert' ];
2016-08-28 12:32:58 -05:00
} else {
2015-07-13 20:10:26 +02:00
$inv = false ;
}
2016-09-25 16:01:58 +01:00
d_echo ( PHP_EOL );
2017-04-18 02:08:52 +01:00
$chk = dbFetchRow ( " SELECT state FROM alerts WHERE rule_id = ? && device_id = ? ORDER BY id DESC LIMIT 1 " , array ( $rule [ 'id' ], $device ));
2016-10-15 00:29:55 +01:00
if ( empty ( $rule [ 'query' ])) {
$rule [ 'query' ] = GenSQL ( $rule [ 'rule' ]);
}
$sql = $rule [ 'query' ];
2016-08-28 12:32:58 -05:00
$qry = dbFetchRows ( $sql , array ( $device ));
2016-12-30 00:50:22 +01:00
$cnt = count ( $qry );
for ( $i = 0 ; $i < $cnt ; $i ++ ) {
if ( isset ( $qry [ $i ][ 'ip' ])) {
$qry [ $i ][ 'ip' ] = inet6_ntop ( $qry [ $i ][ 'ip' ]);
}
2016-02-27 11:51:31 +00:00
}
2015-07-13 20:10:26 +02:00
$s = sizeof ( $qry );
2016-08-28 12:32:58 -05:00
if ( $s == 0 && $inv === false ) {
2015-07-13 20:10:26 +02:00
$doalert = false ;
2016-08-28 12:32:58 -05:00
} elseif ( $s > 0 && $inv === false ) {
2015-07-13 20:10:26 +02:00
$doalert = true ;
2016-08-28 12:32:58 -05:00
} elseif ( $s == 0 && $inv === true ) {
2015-07-13 20:10:26 +02:00
$doalert = true ;
2016-08-28 12:32:58 -05:00
} else { //( $s > 0 && $inv == false ) {
2015-07-13 20:10:26 +02:00
$doalert = false ;
}
2016-08-28 12:32:58 -05:00
if ( $doalert ) {
if ( $chk [ 'state' ] === " 2 " ) {
2016-09-25 16:01:58 +01:00
c_echo ( 'Status: %ySKIP' );
2016-08-28 12:32:58 -05:00
} elseif ( $chk [ 'state' ] >= " 1 " ) {
2017-04-18 02:08:52 +01:00
c_echo ( 'Status: %bNOCHG' );
2017-03-22 09:02:47 +00:00
// NOCHG here doesn't mean no change full stop. It means no change to the alert state
// So we update the details column with any fresh changes to the alert output we might have.
2017-04-18 02:08:52 +01:00
$alert_log = dbFetchRow ( 'SELECT alert_log.id, alert_log.details FROM alert_log,alert_rules WHERE alert_log.rule_id = alert_rules.id && alert_log.device_id = ? && alert_log.rule_id = ? && alert_rules.disabled = 0 ORDER BY alert_log.id DESC LIMIT 1' , array ( $device , $rule [ 'id' ]));
$details = json_decode ( gzuncompress ( $alert_log [ 'details' ]), true );
$details [ 'contacts' ] = GetContacts ( $qry );
$details [ 'rule' ] = $qry ;
$details = gzcompress ( json_encode ( $details ), 9 );
dbUpdate ( array ( 'details' => $details ), 'alert_log' , 'id = ?' , array ( $alert_log [ 'id' ]));
2016-08-28 12:32:58 -05:00
} else {
2017-04-18 02:08:52 +01:00
$extra = gzcompress ( json_encode ( array ( 'contacts' => GetContacts ( $qry ), 'rule' => $qry )), 9 );
2016-08-28 12:32:58 -05:00
if ( dbInsert ( array ( 'state' => 1 , 'device_id' => $device , 'rule_id' => $rule [ 'id' ], 'details' => $extra ), 'alert_log' )) {
if ( ! dbUpdate ( array ( 'state' => 1 , 'open' => 1 ), 'alerts' , 'device_id = ? && rule_id = ?' , array ( $device , $rule [ 'id' ]))) {
dbInsert ( array ( 'state' => 1 , 'device_id' => $device , 'rule_id' => $rule [ 'id' ], 'open' => 1 , 'alerted' => 0 ), 'alerts' );
2015-07-13 20:10:26 +02:00
}
2016-09-25 16:01:58 +01:00
c_echo ( PHP_EOL . 'Status: %rALERT' );
2015-07-13 20:10:26 +02:00
}
}
2016-08-28 12:32:58 -05:00
} else {
if ( $chk [ 'state' ] === " 0 " ) {
2016-09-25 16:01:58 +01:00
c_echo ( 'Status: %bNOCHG' );
2016-08-28 12:32:58 -05:00
} else {
if ( dbInsert ( array ( 'state' => 0 , 'device_id' => $device , 'rule_id' => $rule [ 'id' ]), 'alert_log' )) {
if ( ! dbUpdate ( array ( 'state' => 0 , 'open' => 1 ), 'alerts' , 'device_id = ? && rule_id = ?' , array ( $device , $rule [ 'id' ]))) {
dbInsert ( array ( 'state' => 0 , 'device_id' => $device , 'rule_id' => $rule [ 'id' ], 'open' => 1 , 'alerted' => 0 ), 'alerts' );
2015-07-13 20:10:26 +02:00
}
2016-09-25 16:01:58 +01:00
c_echo ( PHP_EOL . 'Status: %gOK' );
2015-07-13 20:10:26 +02:00
}
}
}
2016-09-25 16:01:58 +01:00
c_echo ( '%n' . PHP_EOL );
2015-07-13 20:10:26 +02:00
}
2012-05-16 16:26:19 +00:00
}
/**
2014-12-15 11:10:26 +00:00
* Find contacts for alert
* @ param array $results Rule - Result
* @ return array
*/
2016-08-28 12:32:58 -05:00
function GetContacts ( $results )
{
2017-11-18 11:33:03 +01:00
global $config , $authorizer ;
2017-12-01 05:27:04 +00:00
2016-08-28 12:32:58 -05:00
if ( sizeof ( $results ) == 0 ) {
2015-07-13 20:10:26 +02:00
return array ();
}
2016-10-14 02:14:02 +01:00
if ( $config [ 'alert' ][ 'default_only' ] === true || $config [ 'alerts' ][ 'email' ][ 'default_only' ] === true ) {
2015-07-13 20:10:26 +02:00
return array ( '' . ( $config [ 'alert' ][ 'default_mail' ] ? $config [ 'alert' ][ 'default_mail' ] : $config [ 'alerts' ][ 'email' ][ 'default' ]) => 'NOC' );
}
2017-11-18 11:33:03 +01:00
$users = Auth :: get () -> getUserlist ();
2015-07-13 20:10:26 +02:00
$contacts = array ();
$uids = array ();
2016-08-28 12:32:58 -05:00
foreach ( $results as $result ) {
$tmp = null ;
if ( is_numeric ( $result [ " bill_id " ])) {
$tmpa = dbFetchRows ( " SELECT user_id FROM bill_perms WHERE bill_id = ? " , array ( $result [ " bill_id " ]));
foreach ( $tmpa as $tmp ) {
2015-07-13 20:10:26 +02:00
$uids [ $tmp [ 'user_id' ]] = $tmp [ 'user_id' ];
}
}
2016-08-28 12:32:58 -05:00
if ( is_numeric ( $result [ " port_id " ])) {
$tmpa = dbFetchRows ( " SELECT user_id FROM ports_perms WHERE access_level >= 0 AND port_id = ? " , array ( $result [ " port_id " ]));
foreach ( $tmpa as $tmp ) {
2015-07-13 20:10:26 +02:00
$uids [ $tmp [ 'user_id' ]] = $tmp [ 'user_id' ];
}
}
2016-08-28 12:32:58 -05:00
if ( is_numeric ( $result [ " device_id " ])) {
if ( $config [ 'alert' ][ 'syscontact' ] == true ) {
if ( dbFetchCell ( " SELECT attrib_value FROM devices_attribs WHERE attrib_type = 'override_sysContact_bool' AND device_id = ? " , array ( $result [ " device_id " ])) === " 1 " ) {
$tmpa = dbFetchCell ( " SELECT attrib_value FROM devices_attribs WHERE attrib_type = 'override_sysContact_string' AND device_id = ? " , array ( $result [ " device_id " ]));
} else {
$tmpa = dbFetchCell ( " SELECT sysContact FROM devices WHERE device_id = ? " , array ( $result [ " device_id " ]));
2015-07-13 20:10:26 +02:00
}
2016-10-14 02:14:02 +01:00
if ( ! empty ( $tmpa )) {
$contacts [ $tmpa ] = " NOC " ;
}
2015-07-13 20:10:26 +02:00
}
$tmpa = dbFetchRows ( " SELECT user_id FROM devices_perms WHERE access_level >= 0 AND device_id = ? " , array ( $result [ " device_id " ]));
2016-08-28 12:32:58 -05:00
foreach ( $tmpa as $tmp ) {
2015-07-13 20:10:26 +02:00
$uids [ $tmp [ 'user_id' ]] = $tmp [ 'user_id' ];
}
}
}
2016-08-28 12:32:58 -05:00
foreach ( $users as $user ) {
if ( empty ( $user [ 'email' ])) {
2017-03-29 08:22:02 -05:00
continue ; // no email, skip this user
}
if ( empty ( $user [ 'realname' ])) {
2015-07-13 20:10:26 +02:00
$user [ 'realname' ] = $user [ 'username' ];
}
2017-03-29 08:22:02 -05:00
if ( empty ( $user [ 'level' ])) {
2017-11-18 11:33:03 +01:00
$user [ 'level' ] = Auth :: get () -> getUserlevel ( $user [ 'username' ]);
2017-03-29 08:22:02 -05:00
}
2017-05-15 23:35:26 +01:00
if ( $config [ 'alert' ][ 'globals' ] && ( $user [ 'level' ] >= 5 && $user [ 'level' ] < 10 )) {
2015-07-13 20:10:26 +02:00
$contacts [ $user [ 'email' ]] = $user [ 'realname' ];
2017-05-15 23:35:26 +01:00
} elseif ( $config [ 'alert' ][ 'admins' ] && $user [ 'level' ] == 10 ) {
2015-07-13 20:10:26 +02:00
$contacts [ $user [ 'email' ]] = $user [ 'realname' ];
2017-05-15 23:35:26 +01:00
} elseif ( $config [ 'alert' ][ 'users' ] == true && in_array ( $user [ 'user_id' ], $uids )) {
2015-07-13 20:10:26 +02:00
$contacts [ $user [ 'email' ]] = $user [ 'realname' ];
}
}
2016-07-26 22:29:04 +01:00
$tmp_contacts = array ();
foreach ( $contacts as $email => $name ) {
if ( strstr ( $email , ',' )) {
2017-03-17 16:29:23 -05:00
$split_contacts = preg_split ( '/[,\s]+/' , $email );
2016-07-26 22:29:04 +01:00
foreach ( $split_contacts as $split_email ) {
2016-08-28 12:32:58 -05:00
if ( ! empty ( $split_email )) {
2016-07-28 20:28:23 +01:00
$tmp_contacts [ $split_email ] = $name ;
}
2016-07-26 22:29:04 +01:00
}
} else {
$tmp_contacts [ $email ] = $name ;
}
}
2017-12-01 05:27:04 +00:00
if ( ! empty ( $tmp_contacts )) {
// Validate contacts so we can fall back to default if configured.
$mail = new PHPMailer ();
foreach ( $tmp_contacts as $tmp_email => $tmp_name ) {
if ( $mail -> validateAddress ( $tmp_email ) != true ) {
unset ( $tmp_contacts [ $tmp_email ]);
}
}
}
2017-03-14 18:22:43 -05:00
# Send email to default contact if no other contact found
if (( count ( $tmp_contacts ) == 0 ) && ( $config [ 'alert' ][ 'default_if_none' ]) && ( ! empty ( $config [ 'alert' ][ 'default_mail' ]))) {
$tmp_contacts [ $config [ 'alert' ][ 'default_mail' ]] = 'NOC' ;
}
2016-07-26 22:29:04 +01:00
return $tmp_contacts ;
2012-05-16 16:26:19 +00:00
}
2017-05-13 12:46:08 +01:00
/**
* Format Alert
* @ param array $obj Alert - Array
* @ return string
*/
function FormatAlertTpl ( $obj )
{
$tpl = $obj [ " template " ];
2017-05-17 20:11:54 +01:00
$msg = '$ret .= "' . str_replace ( array ( '{else}' , '{/if}' , '{/foreach}' ), array ( '"; } else { $ret .= "' , '"; } $ret .= "' , '"; } $ret .= "' ), addslashes ( $tpl )) . '";' ;
2017-05-13 12:46:08 +01:00
$parsed = $msg ;
$s = strlen ( $msg );
$x = $pos = - 1 ;
$buff = '' ;
$if = $for = $calc = false ;
while ( ++ $x < $s ) {
if ( $msg [ $x ] == '{' && $buff == '' ) {
$buff .= $msg [ $x ];
} elseif ( $buff == '{ ' ) {
$buff = '' ;
} elseif ( $buff != '' ) {
$buff .= $msg [ $x ];
}
if ( $buff == '{if' ) {
$pos = $x ;
$if = true ;
} elseif ( $buff == '{foreach' ) {
$pos = $x ;
$for = true ;
} elseif ( $buff == '{calc' ) {
$pos = $x ;
$calc = true ;
}
if ( $pos != - 1 && $msg [ $x ] == '}' ) {
$orig = $buff ;
$buff = '' ;
$pos = - 1 ;
if ( $if ) {
$if = false ;
$o = 3 ;
$native = array (
'"; if( ' ,
' ) { $ret .= "' ,
);
} elseif ( $for ) {
$for = false ;
$o = 8 ;
$native = array (
'"; foreach( ' ,
' as $key=>$value) { $ret .= "' ,
);
} elseif ( $calc ) {
$calc = false ;
$o = 5 ;
$native = array (
'"; $ret .= (float) (0+(' ,
')); $ret .= "' ,
);
} else {
continue ;
}
$cond = trim ( populate ( substr ( $orig , $o , - 1 ), false ));
$native = $native [ 0 ] . $cond . $native [ 1 ];
$parsed = str_replace ( $orig , $native , $parsed );
unset ( $cond , $o , $orig , $native );
} //end if
} //end while
$parsed = populate ( $parsed );
return RunJail ( $parsed , $obj );
} //end FormatAlertTpl()
/**
* Populate variables
* @ param string $txt Text with variables
* @ param boolean $wrap Wrap variable for text - usage ( default : true )
* @ return string
*/
function populate ( $txt , $wrap = true )
{
preg_match_all ( '/%([\w\.]+)/' , $txt , $m );
foreach ( $m [ 1 ] as $tmp ) {
$orig = $tmp ;
$rep = false ;
if ( $tmp == 'key' || $tmp == 'value' ) {
$rep = '$' . $tmp ;
} else {
if ( strstr ( $tmp , '.' )) {
$tmp = explode ( '.' , $tmp , 2 );
$pre = '$' . $tmp [ 0 ];
$tmp = $tmp [ 1 ];
} else {
$pre = '$obj' ;
}
$rep = $pre . " [' " . str_replace ( '.' , " '][' " , $tmp ) . " '] " ;
if ( $wrap ) {
$rep = '{' . $rep . '}' ;
}
}
$txt = str_replace ( '%' . $orig , $rep , $txt );
} //end foreach
return $txt ;
} //end populate()
/**
* " Safely " run eval
* @ param string $code Code to run
* @ param array $obj Object with variables
* @ return string | mixed
*/
function RunJail ( $code , $obj )
{
$ret = '' ;
eval ( $code );
return $ret ;
} //end RunJail()
/**
* Describe Alert
* @ param array $alert Alert - Result from DB
* @ return array | boolean
*/
function DescribeAlert ( $alert )
{
$obj = array ();
$i = 0 ;
2017-06-01 04:16:39 -07:00
$device = dbFetchRow ( 'SELECT hostname, sysName, sysDescr, hardware, version, location, purpose, notes, uptime FROM devices WHERE device_id = ?' , array ( $alert [ 'device_id' ]));
2017-05-13 12:46:08 +01:00
$tpl = dbFetchRow ( 'SELECT `template`,`title`,`title_rec` FROM `alert_templates` JOIN `alert_template_map` ON `alert_template_map`.`alert_templates_id`=`alert_templates`.`id` WHERE `alert_template_map`.`alert_rule_id`=?' , array ( $alert [ 'rule_id' ]));
2017-08-12 22:21:13 +03:00
if ( ! $tpl ) {
$tpl = dbFetchRow ( " SELECT `template`,`title`,`title_rec` FROM `alert_templates` WHERE `name`='Default Alert Template' " );
}
2017-05-13 12:46:08 +01:00
$default_tpl = " %title \r \n Severity: %severity \r \n { if %state == 0}Time elapsed: %elapsed \r \n { /if}Timestamp: %timestamp \r \n Unique-ID: %uid \r \n Rule: { if %name}%name { else}%rule { /if} \r \n { if %faults}Faults: \r \n { foreach %faults} #%key: %value.string \r \n { /foreach} { /if}Alert sent to: { foreach %contacts}%value <%key> { /foreach} " ;
$obj [ 'hostname' ] = $device [ 'hostname' ];
$obj [ 'sysName' ] = $device [ 'sysName' ];
2017-06-01 04:16:39 -07:00
$obj [ 'sysDescr' ] = $device [ 'sysDescr' ];
$obj [ 'hardware' ] = $device [ 'hardware' ];
$obj [ 'version' ] = $device [ 'version' ];
2017-05-13 12:46:08 +01:00
$obj [ 'location' ] = $device [ 'location' ];
$obj [ 'uptime' ] = $device [ 'uptime' ];
$obj [ 'uptime_short' ] = formatUptime ( $device [ 'uptime' ], 'short' );
$obj [ 'uptime_long' ] = formatUptime ( $device [ 'uptime' ]);
$obj [ 'description' ] = $device [ 'purpose' ];
$obj [ 'notes' ] = $device [ 'notes' ];
$obj [ 'device_id' ] = $alert [ 'device_id' ];
$extra = $alert [ 'details' ];
if ( ! isset ( $tpl [ 'template' ])) {
$obj [ 'template' ] = $default_tpl ;
} else {
$obj [ 'template' ] = $tpl [ 'template' ];
}
if ( $alert [ 'state' ] >= 1 ) {
if ( ! empty ( $tpl [ 'title' ])) {
$obj [ 'title' ] = $tpl [ 'title' ];
} else {
$obj [ 'title' ] = 'Alert for device ' . $device [ 'hostname' ] . ' - ' . ( $alert [ 'name' ] ? $alert [ 'name' ] : $alert [ 'rule' ]);
}
if ( $alert [ 'state' ] == 2 ) {
$obj [ 'title' ] .= ' got acknowledged' ;
} elseif ( $alert [ 'state' ] == 3 ) {
$obj [ 'title' ] .= ' got worse' ;
} elseif ( $alert [ 'state' ] == 4 ) {
$obj [ 'title' ] .= ' got better' ;
}
foreach ( $extra [ 'rule' ] as $incident ) {
$i ++ ;
$obj [ 'faults' ][ $i ] = $incident ;
foreach ( $incident as $k => $v ) {
if ( ! empty ( $v ) && $k != 'device_id' && ( stristr ( $k , 'id' ) || stristr ( $k , 'desc' ) || stristr ( $k , 'msg' )) && substr_count ( $k , '_' ) <= 1 ) {
$obj [ 'faults' ][ $i ][ 'string' ] .= $k . ' => ' . $v . '; ' ;
}
}
}
$obj [ 'elapsed' ] = TimeFormat ( time () - strtotime ( $alert [ 'time_logged' ]));
if ( ! empty ( $extra [ 'diff' ])) {
$obj [ 'diff' ] = $extra [ 'diff' ];
}
} elseif ( $alert [ 'state' ] == 0 ) {
$id = dbFetchRow ( 'SELECT alert_log.id,alert_log.time_logged,alert_log.details FROM alert_log WHERE alert_log.state != 2 && alert_log.state != 0 && alert_log.rule_id = ? && alert_log.device_id = ? && alert_log.id < ? ORDER BY id DESC LIMIT 1' , array ( $alert [ 'rule_id' ], $alert [ 'device_id' ], $alert [ 'id' ]));
if ( empty ( $id [ 'id' ])) {
return false ;
}
$extra = json_decode ( gzuncompress ( $id [ 'details' ]), true );
if ( ! empty ( $tpl [ 'title_rec' ])) {
$obj [ 'title' ] = $tpl [ 'title_rec' ];
} else {
$obj [ 'title' ] = 'Device ' . $device [ 'hostname' ] . ' recovered from ' . ( $alert [ 'name' ] ? $alert [ 'name' ] : $alert [ 'rule' ]);
}
$obj [ 'elapsed' ] = TimeFormat ( strtotime ( $alert [ 'time_logged' ]) - strtotime ( $id [ 'time_logged' ]));
$obj [ 'id' ] = $id [ 'id' ];
foreach ( $extra [ 'rule' ] as $incident ) {
$i ++ ;
$obj [ 'faults' ][ $i ] = $incident ;
foreach ( $incident as $k => $v ) {
if ( ! empty ( $v ) && $k != 'device_id' && ( stristr ( $k , 'id' ) || stristr ( $k , 'desc' ) || stristr ( $k , 'msg' )) && substr_count ( $k , '_' ) <= 1 ) {
$obj [ 'faults' ][ $i ][ 'string' ] .= $k . ' => ' . $v . '; ' ;
}
}
}
} else {
return 'Unknown State' ;
} //end if
$obj [ 'uid' ] = $alert [ 'id' ];
$obj [ 'severity' ] = $alert [ 'severity' ];
$obj [ 'rule' ] = $alert [ 'rule' ];
$obj [ 'name' ] = $alert [ 'name' ];
$obj [ 'timestamp' ] = $alert [ 'time_logged' ];
$obj [ 'contacts' ] = $extra [ 'contacts' ];
$obj [ 'state' ] = $alert [ 'state' ];
if ( strstr ( $obj [ 'title' ], '%' )) {
$obj [ 'title' ] = RunJail ( '$ret = "' . populate ( addslashes ( $obj [ 'title' ])) . '";' , $obj );
}
return $obj ;
} //end DescribeAlert()
/**
* Format Elapsed Time
* @ param integer $secs Seconds elapsed
* @ return string
*/
function TimeFormat ( $secs )
{
$bit = array (
'y' => $secs / 31556926 % 12 ,
'w' => $secs / 604800 % 52 ,
'd' => $secs / 86400 % 7 ,
'h' => $secs / 3600 % 24 ,
'm' => $secs / 60 % 60 ,
's' => $secs % 60 ,
);
$ret = array ();
foreach ( $bit as $k => $v ) {
if ( $v > 0 ) {
$ret [] = $v . $k ;
}
}
if ( empty ( $ret )) {
return 'none' ;
}
return join ( ' ' , $ret );
} //end TimeFormat()
function ClearStaleAlerts ()
{
$sql = " SELECT `alerts`.`id` AS `alert_id`, `devices`.`hostname` AS `hostname` FROM `alerts` LEFT JOIN `devices` ON `alerts`.`device_id`=`devices`.`device_id` RIGHT JOIN `alert_rules` ON `alerts`.`rule_id`=`alert_rules`.`id` WHERE `alerts`.`state`!=0 AND `devices`.`hostname` IS NULL " ;
foreach ( dbFetchRows ( $sql ) as $alert ) {
if ( empty ( $alert [ 'hostname' ]) && isset ( $alert [ 'alert_id' ])) {
dbDelete ( 'alerts' , '`id` = ?' , array ( $alert [ 'alert_id' ]));
echo " Stale-alert: # { $alert [ 'alert_id' ] } " . PHP_EOL ;
}
}
}
/**
* Re - Validate Rule - Mappings
* @ param integer $device Device - ID
* @ param integer $rule Rule - ID
* @ return boolean
*/
function IsRuleValid ( $device , $rule )
{
global $rulescache ;
if ( empty ( $rulescache [ $device ]) || ! isset ( $rulescache [ $device ])) {
foreach ( GetRules ( $device ) as $chk ) {
$rulescache [ $device ][ $chk [ 'id' ]] = true ;
}
}
if ( $rulescache [ $device ][ $rule ] === true ) {
return true ;
}
return false ;
} //end IsRuleValid()
/**
* Issue Alert - Object
* @ param array $alert
* @ return boolean
*/
function IssueAlert ( $alert )
{
global $config ;
if ( dbFetchCell ( 'SELECT attrib_value FROM devices_attribs WHERE attrib_type = "disable_notify" && device_id = ?' , array ( $alert [ 'device_id' ])) == '1' ) {
return true ;
}
if ( $config [ 'alert' ][ 'fixed-contacts' ] == false ) {
if ( empty ( $alert [ 'query' ])) {
$alert [ 'query' ] = GenSQL ( $alert [ 'rule' ]);
}
$sql = $alert [ 'query' ];
$qry = dbFetchRows ( $sql , array ( $alert [ 'device_id' ]));
$alert [ 'details' ][ 'contacts' ] = GetContacts ( $qry );
}
$obj = DescribeAlert ( $alert );
if ( is_array ( $obj )) {
echo 'Issuing Alert-UID #' . $alert [ 'id' ] . '/' . $alert [ 'state' ] . ': ' ;
if ( ! empty ( $config [ 'alert' ][ 'transports' ])) {
ExtTransports ( $obj );
}
echo " \r \n " ;
}
return true ;
} //end IssueAlert()
/**
* Issue ACK notification
* @ return void
*/
function RunAcks ()
{
foreach ( dbFetchRows ( 'SELECT alerts.device_id, alerts.rule_id, alerts.state FROM alerts WHERE alerts.state = 2 && alerts.open = 1' ) as $alert ) {
$tmp = array (
$alert [ 'rule_id' ],
$alert [ 'device_id' ],
);
$alert = dbFetchRow ( 'SELECT alert_log.id,alert_log.rule_id,alert_log.device_id,alert_log.state,alert_log.details,alert_log.time_logged,alert_rules.rule,alert_rules.severity,alert_rules.extra,alert_rules.name FROM alert_log,alert_rules WHERE alert_log.rule_id = alert_rules.id && alert_log.device_id = ? && alert_log.rule_id = ? && alert_rules.disabled = 0 ORDER BY alert_log.id DESC LIMIT 1' , array ( $alert [ 'device_id' ], $alert [ 'rule_id' ]));
if ( empty ( $alert [ 'rule' ]) || ! IsRuleValid ( $tmp [ 1 ], $tmp [ 0 ])) {
// Alert-Rule does not exist anymore, let's remove the alert-state.
echo 'Stale-Rule: #' . $tmp [ 0 ] . '/' . $tmp [ 1 ] . " \r \n " ;
dbDelete ( 'alerts' , 'rule_id = ? && device_id = ?' , array ( $tmp [ 0 ], $tmp [ 1 ]));
continue ;
}
$alert [ 'details' ] = json_decode ( gzuncompress ( $alert [ 'details' ]), true );
$alert [ 'state' ] = 2 ;
IssueAlert ( $alert );
dbUpdate ( array ( 'open' => 0 ), 'alerts' , 'rule_id = ? && device_id = ?' , array ( $alert [ 'rule_id' ], $alert [ 'device_id' ]));
}
} //end RunAcks()
/**
* Run Follow - Up alerts
* @ return void
*/
function RunFollowUp ()
{
global $config ;
foreach ( dbFetchRows ( 'SELECT alerts.device_id, alerts.rule_id, alerts.state FROM alerts WHERE alerts.state != 2 && alerts.state > 0 && alerts.open = 0' ) as $alert ) {
$tmp = array (
$alert [ 'rule_id' ],
$alert [ 'device_id' ],
);
$alert = dbFetchRow ( 'SELECT alert_log.id,alert_log.rule_id,alert_log.device_id,alert_log.state,alert_log.details,alert_log.time_logged,alert_rules.rule, alert_rules.query,alert_rules.severity,alert_rules.extra,alert_rules.name FROM alert_log,alert_rules WHERE alert_log.rule_id = alert_rules.id && alert_log.device_id = ? && alert_log.rule_id = ? && alert_rules.disabled = 0 ORDER BY alert_log.id DESC LIMIT 1' , array ( $alert [ 'device_id' ], $alert [ 'rule_id' ]));
if ( empty ( $alert [ 'rule' ]) || ! IsRuleValid ( $tmp [ 1 ], $tmp [ 0 ])) {
// Alert-Rule does not exist anymore, let's remove the alert-state.
echo 'Stale-Rule: #' . $tmp [ 0 ] . '/' . $tmp [ 1 ] . " \r \n " ;
dbDelete ( 'alerts' , 'rule_id = ? && device_id = ?' , array ( $tmp [ 0 ], $tmp [ 1 ]));
continue ;
}
$alert [ 'details' ] = json_decode ( gzuncompress ( $alert [ 'details' ]), true );
$rextra = json_decode ( $alert [ 'extra' ], true );
if ( $rextra [ 'invert' ]) {
continue ;
}
if ( empty ( $alert [ 'query' ])) {
$alert [ 'query' ] = GenSQL ( $alert [ 'rule' ]);
}
$chk = dbFetchRows ( $alert [ 'query' ], array ( $alert [ 'device_id' ]));
//make sure we can json_encode all the datas later
$cnt = count ( $chk );
for ( $i = 0 ; $i < $cnt ; $i ++ ) {
if ( isset ( $chk [ $i ][ 'ip' ])) {
$chk [ $i ][ 'ip' ] = inet6_ntop ( $chk [ $i ][ 'ip' ]);
}
}
$o = sizeof ( $alert [ 'details' ][ 'rule' ]);
$n = sizeof ( $chk );
$ret = 'Alert #' . $alert [ 'id' ];
$state = 0 ;
if ( $n > $o ) {
$ret .= ' Worsens' ;
$state = 3 ;
$alert [ 'details' ][ 'diff' ] = array_diff ( $chk , $alert [ 'details' ][ 'rule' ]);
} elseif ( $n < $o ) {
$ret .= ' Betters' ;
$state = 4 ;
$alert [ 'details' ][ 'diff' ] = array_diff ( $alert [ 'details' ][ 'rule' ], $chk );
}
if ( $state > 0 && $n > 0 ) {
$alert [ 'details' ][ 'rule' ] = $chk ;
if ( dbInsert ( array ( 'state' => $state , 'device_id' => $alert [ 'device_id' ], 'rule_id' => $alert [ 'rule_id' ], 'details' => gzcompress ( json_encode ( $alert [ 'details' ]), 9 )), 'alert_log' )) {
dbUpdate ( array ( 'state' => $state , 'open' => 1 , 'alerted' => 1 ), 'alerts' , 'rule_id = ? && device_id = ?' , array ( $alert [ 'rule_id' ], $alert [ 'device_id' ]));
}
echo $ret . ' (' . $o . '/' . $n . " ) \r \n " ;
}
} //end foreach
} //end RunFollowUp()
/**
* Run all alerts
* @ return void
*/
function RunAlerts ()
{
global $config ;
foreach ( dbFetchRows ( 'SELECT alerts.device_id, alerts.rule_id, alerts.state FROM alerts WHERE alerts.state != 2 && alerts.open = 1' ) as $alert ) {
$tmp = array (
$alert [ 'rule_id' ],
$alert [ 'device_id' ],
);
$alert = dbFetchRow ( 'SELECT alert_log.id,alert_log.rule_id,alert_log.device_id,alert_log.state,alert_log.details,alert_log.time_logged,alert_rules.rule,alert_rules.severity,alert_rules.extra,alert_rules.name FROM alert_log,alert_rules WHERE alert_log.rule_id = alert_rules.id && alert_log.device_id = ? && alert_log.rule_id = ? && alert_rules.disabled = 0 ORDER BY alert_log.id DESC LIMIT 1' , array ( $alert [ 'device_id' ], $alert [ 'rule_id' ]));
if ( empty ( $alert [ 'rule_id' ]) || ! IsRuleValid ( $tmp [ 1 ], $tmp [ 0 ])) {
echo 'Stale-Rule: #' . $tmp [ 0 ] . '/' . $tmp [ 1 ] . " \r \n " ;
// Alert-Rule does not exist anymore, let's remove the alert-state.
dbDelete ( 'alerts' , 'rule_id = ? && device_id = ?' , array ( $tmp [ 0 ], $tmp [ 1 ]));
continue ;
}
$alert [ 'details' ] = json_decode ( gzuncompress ( $alert [ 'details' ]), true );
$noiss = false ;
$noacc = false ;
$updet = false ;
$rextra = json_decode ( $alert [ 'extra' ], true );
$chk = dbFetchRow ( 'SELECT alerts.alerted,devices.ignore,devices.disabled FROM alerts,devices WHERE alerts.device_id = ? && devices.device_id = alerts.device_id && alerts.rule_id = ?' , array ( $alert [ 'device_id' ], $alert [ 'rule_id' ]));
if ( $chk [ 'alerted' ] == $alert [ 'state' ]) {
$noiss = true ;
}
if ( ! empty ( $rextra [ 'count' ]) && empty ( $rextra [ 'interval' ])) {
// This check below is for compat-reasons
if ( ! empty ( $rextra [ 'delay' ])) {
if (( time () - strtotime ( $alert [ 'time_logged' ]) + $config [ 'alert' ][ 'tolerance_window' ]) < $rextra [ 'delay' ] || ( ! empty ( $alert [ 'details' ][ 'delay' ]) && ( time () - $alert [ 'details' ][ 'delay' ] + $config [ 'alert' ][ 'tolerance_window' ]) < $rextra [ 'delay' ])) {
continue ;
} else {
$alert [ 'details' ][ 'delay' ] = time ();
$updet = true ;
}
}
if ( $alert [ 'state' ] == 1 && ! empty ( $rextra [ 'count' ]) && ( $rextra [ 'count' ] == - 1 || $alert [ 'details' ][ 'count' ] ++ < $rextra [ 'count' ])) {
if ( $alert [ 'details' ][ 'count' ] < $rextra [ 'count' ]) {
$noacc = true ;
}
$updet = true ;
$noiss = false ;
}
} else {
// This is the new way
if ( ! empty ( $rextra [ 'delay' ]) && ( time () - strtotime ( $alert [ 'time_logged' ]) + $config [ 'alert' ][ 'tolerance_window' ]) < $rextra [ 'delay' ]) {
continue ;
}
if ( ! empty ( $rextra [ 'interval' ])) {
if ( ! empty ( $alert [ 'details' ][ 'interval' ]) && ( time () - $alert [ 'details' ][ 'interval' ] + $config [ 'alert' ][ 'tolerance_window' ]) < $rextra [ 'interval' ]) {
continue ;
} else {
$alert [ 'details' ][ 'interval' ] = time ();
$updet = true ;
}
}
if ( $alert [ 'state' ] == 1 && ! empty ( $rextra [ 'count' ]) && ( $rextra [ 'count' ] == - 1 || $alert [ 'details' ][ 'count' ] ++ < $rextra [ 'count' ])) {
if ( $alert [ 'details' ][ 'count' ] < $rextra [ 'count' ]) {
$noacc = true ;
}
$updet = true ;
$noiss = false ;
}
} //end if
if ( $chk [ 'ignore' ] == 1 || $chk [ 'disabled' ] == 1 ) {
$noiss = true ;
$updet = false ;
$noacc = false ;
}
if ( IsMaintenance ( $alert [ 'device_id' ]) > 0 ) {
$noiss = true ;
$noacc = true ;
}
if ( $updet ) {
dbUpdate ( array ( 'details' => gzcompress ( json_encode ( $alert [ 'details' ]), 9 )), 'alert_log' , 'id = ?' , array ( $alert [ 'id' ]));
}
if ( ! empty ( $rextra [ 'mute' ])) {
echo 'Muted Alert-UID #' . $alert [ 'id' ] . " \r \n " ;
$noiss = true ;
}
if ( ! $noiss ) {
IssueAlert ( $alert );
dbUpdate ( array ( 'alerted' => $alert [ 'state' ]), 'alerts' , 'rule_id = ? && device_id = ?' , array ( $alert [ 'rule_id' ], $alert [ 'device_id' ]));
}
if ( ! $noacc ) {
dbUpdate ( array ( 'open' => 0 ), 'alerts' , 'rule_id = ? && device_id = ?' , array ( $alert [ 'rule_id' ], $alert [ 'device_id' ]));
}
} //end foreach
} //end RunAlerts()
/**
* Run external transports
* @ param array $obj Alert - Array
* @ return void
*/
function ExtTransports ( $obj )
{
global $config ;
$tmp = false ;
// To keep scrutinizer from naging because it doesnt understand eval
foreach ( $config [ 'alert' ][ 'transports' ] as $transport => $opts ) {
if ( is_array ( $opts )) {
$opts = array_filter ( $opts );
}
2017-12-10 21:20:28 +01:00
$class = 'LibreNMS\\Alert\\Transport\\' . ucfirst ( $transport );
if (( $opts === true || ! empty ( $opts )) && $opts != false && class_exists ( $class )) {
2017-05-13 12:46:08 +01:00
$obj [ 'transport' ] = $transport ;
$msg = FormatAlertTpl ( $obj );
$obj [ 'msg' ] = $msg ;
echo $transport . ' => ' ;
2017-12-10 21:20:28 +01:00
$instance = new $class ;
$tmp = $instance -> deliverAlert ( $obj , $opts );
2017-05-13 12:46:08 +01:00
$prefix = array ( 0 => " recovery " , 1 => $obj [ 'severity' ] . " alert " , 2 => " acknowledgment " );
$prefix [ 3 ] = & $prefix [ 0 ];
$prefix [ 4 ] = & $prefix [ 0 ];
if ( $tmp === true ) {
echo 'OK' ;
2017-07-13 15:30:05 +03:00
log_event ( 'Issued ' . $prefix [ $obj [ 'state' ]] . " for rule ' " . $obj [ 'name' ] . " ' to transport ' " . $transport . " ' " , $obj [ 'device_id' ], 'alert' , 1 );
2017-05-13 12:46:08 +01:00
} elseif ( $tmp === false ) {
echo 'ERROR' ;
log_event ( 'Could not issue ' . $prefix [ $obj [ 'state' ]] . " for rule ' " . $obj [ 'name' ] . " ' to transport ' " . $transport . " ' " , $obj [ 'device_id' ], null , 5 );
} else {
echo " ERROR: $tmp\r\n " ;
2017-07-13 15:30:05 +03:00
log_event ( 'Could not issue ' . $prefix [ $obj [ 'state' ]] . " for rule ' " . $obj [ 'name' ] . " ' to transport ' " . $transport . " ' Error: " . $tmp , $obj [ 'device_id' ], 'error' , 5 );
2017-05-13 12:46:08 +01:00
}
}
echo '; ' ;
}
} //end ExtTransports()