#!/usr/bin/env php * 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. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * Alerts Cronjob * @author f0o * @copyright 2014 f0o, LibreNMS * @license GPL * @package LibreNMS * @subpackage Alerts */ include_once("includes/defaults.inc.php"); include_once("config.php"); $lock = false; if( file_exists($config['install_dir']."/.alerts.lock") ) { $pids = explode("\n", trim(`ps -e | grep php | awk '{print $1}'`)); $lpid = trim(file_get_contents($config['install_dir']."/.alerts.lock")); if( in_array($lpid,$pids) ) { $lock = true; } } if( $lock === true ) { exit(1); } else { file_put_contents($config['install_dir']."/.alerts.lock", getmypid()); } include_once($config['install_dir']."/includes/definitions.inc.php"); include_once($config['install_dir']."/includes/functions.php"); include_once($config['install_dir']."/includes/alerts.inc.php"); if( !defined("TEST") ) { echo "Start: ".date('r')."\r\n"; echo "RunFollowUp():\r\n"; RunFollowUp(); echo "RunAlerts():\r\n"; RunAlerts(); echo "RunAcks():\r\n"; RunAcks(); echo "End : ".date('r')."\r\n"; } unlink($config['install_dir']."/.alerts.lock"); /** * Re-Validate Rule-Mappings * @param int $device Device-ID * @param int $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; } /** * 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; } $default_tpl = "%title\r\nSeverity: %severity\r\n{if %state == 0}Time elapsed: %elapsed\r\n{/if}Timestamp: %timestamp\r\nUnique-ID: %uid\r\nRule: {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}"; //FIXME: Put somewhere else? $obj = DescribeAlert($alert); if( is_array($obj) ) { $tpl = dbFetchRow('SELECT template FROM alert_templates WHERE rule_id LIKE "%,'.$alert['rule_id'].',%"'); if( isset($tpl['template']) ) { $tpl = $tpl['template']; } else { $tpl = $default_tpl; } echo "Issuing Alert-UID #".$alert['id']."/".$alert['state'].": "; $msg = FormatAlertTpl($tpl,$obj); $obj['msg'] = $msg; if( !empty($config['alert']['transports']) ) { ExtTransports($obj); } echo "\r\n"; } return true; } /** * 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'])); } } /** * 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.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; } $chk = dbFetchRows(GenSQL($alert['rule']),array($alert['device_id'])); $o = sizeof($alert['details']['rule']); $n = sizeof($chk); $ret = "Alert #".$alert['id']; $state = 0; if( $n > $o ) { $ret .= " Worsens"; $state = 3; } elseif( $n < $o ) { $ret .= " Betters"; $state = 4; } if( $state > 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"; } } } /** * 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['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; } 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'])); } } } /** * 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( ($opts === true || !empty($opts)) && file_exists($config['install_dir']."/includes/alerts/transport.".$transport.".php") ) { echo $transport." => "; eval('$tmp = function($obj,$opts) { global $config; '.file_get_contents($config['install_dir']."/includes/alerts/transport.".$transport.".php").' };'); $tmp = $tmp($obj,$opts); echo ($tmp ? "OK" : "ERROR")."; "; } } } /** * Format Alert * @param string $tpl Template * @param array $obj Alert-Array * @return string */ function FormatAlertTpl($tpl,$obj) { $msg = '$ret .= "'.str_replace(array("{else}","{/if}","{/foreach}"),array('"; } else { $ret .= "','"; } $ret .= "','"; } $ret .= "'),addslashes($tpl)).'";'; $parsed = $msg; $s = strlen($msg); $x = $pos = -1; $buff = ""; $if = $for = 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; } 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 .= "'); } 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); } } $parsed = populate($parsed); return RunJail($parsed,$obj); } /** * Describe Alert * @param array $alert Alert-Result from DB * @return array */ function DescribeAlert($alert) { $obj = array(); $i = 0; $device = dbFetchRow("SELECT hostname FROM devices WHERE device_id = ?",array($alert['device_id'])); $obj['hostname'] = $device['hostname']; $obj['device_id'] = $alert['device_id']; $extra = $alert['details']; if( $alert['state'] >= 1 ) { $obj['title'] = 'Alert for device '.$device['hostname'].' Alert-ID #'.$alert['id']; 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."; "; } } } } 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); $obj['title'] = 'Device '.$device['hostname'].' recovered from Alert-ID #'.$id['id']; $obj['elapsed'] = TimeFormat(strtotime($alert['time_logged'])-strtotime($id['time_logged'])); $obj['id'] = $id['id']; $obj['faults'] = false; } else { return "Unknown State"; } $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']; return $obj; } /** * Format Elapsed Time * @param int $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); } /** * "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; } /** * Populate variables * @param string $txt Text with variables * @param bool $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); } return $txt; } ?>