1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* RunAlerts.php |
4
|
|
|
* |
5
|
|
|
* Copyright (C) 2014 Daniel Preussker <[email protected]> |
6
|
|
|
* This program is free software: you can redistribute it and/or modify |
7
|
|
|
* it under the terms of the GNU General Public License as published by |
8
|
|
|
* the Free Software Foundation, either version 3 of the License, or |
9
|
|
|
* (at your option) any later version. |
10
|
|
|
* |
11
|
|
|
* This program is distributed in the hope that it will be useful, |
12
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
13
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the |
14
|
|
|
* GNU General Public License for more details. |
15
|
|
|
* |
16
|
|
|
* You should have received a copy of the GNU General Public License |
17
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
18
|
|
|
* |
19
|
|
|
* Original Code: |
20
|
|
|
* @author Daniel Preussker <[email protected]> |
21
|
|
|
* @copyright 2014 f0o, LibreNMS |
22
|
|
|
* @license GPL |
23
|
|
|
* @package LibreNMS |
24
|
|
|
* @subpackage Alerts |
25
|
|
|
* |
26
|
|
|
* Modified by: |
27
|
|
|
* @author Heath Barnhart <[email protected]> |
28
|
|
|
* |
29
|
|
|
*/ |
30
|
|
|
|
31
|
|
|
namespace LibreNMS\Alert; |
32
|
|
|
|
33
|
|
|
use App\Models\DevicePerf; |
34
|
|
|
use LibreNMS\Config; |
35
|
|
|
use LibreNMS\Util\Time; |
36
|
|
|
use Log; |
37
|
|
|
|
38
|
|
|
class RunAlerts |
39
|
|
|
{ |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Populate variables |
43
|
|
|
* @param string $txt Text with variables |
44
|
|
|
* @param boolean $wrap Wrap variable for text-usage (default: true) |
45
|
|
|
* @return string |
46
|
|
|
*/ |
47
|
|
|
public function populate($txt, $wrap = true) |
48
|
|
|
{ |
49
|
|
|
preg_match_all('/%([\w\.]+)/', $txt, $m); |
50
|
|
|
foreach ($m[1] as $tmp) { |
51
|
|
|
$orig = $tmp; |
52
|
|
|
$rep = false; |
53
|
|
|
if ($tmp == 'key' || $tmp == 'value') { |
54
|
|
|
$rep = '$'.$tmp; |
55
|
|
|
} else { |
56
|
|
|
if (strstr($tmp, '.')) { |
57
|
|
|
$tmp = explode('.', $tmp, 2); |
58
|
|
|
$pre = '$'.$tmp[0]; |
59
|
|
|
$tmp = $tmp[1]; |
60
|
|
|
} else { |
61
|
|
|
$pre = '$obj'; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
$rep = $pre."['".str_replace('.', "']['", $tmp)."']"; |
65
|
|
|
if ($wrap) { |
66
|
|
|
$rep = '{'.$rep.'}'; |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
$txt = str_replace('%'.$orig, $rep, $txt); |
71
|
|
|
} |
72
|
|
|
return $txt; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Describe Alert |
77
|
|
|
* @param array $alert Alert-Result from DB |
78
|
|
|
* @return array|boolean |
79
|
|
|
*/ |
80
|
|
|
public function describeAlert($alert) |
81
|
|
|
{ |
82
|
|
|
$obj = array(); |
83
|
|
|
$i = 0; |
84
|
|
|
$device = dbFetchRow('SELECT hostname, sysName, sysDescr, sysContact, os, type, ip, hardware, version, purpose, notes, uptime, status, status_reason, locations.location FROM devices LEFT JOIN locations ON locations.id = devices.location_id WHERE device_id = ?', array($alert['device_id'])); |
85
|
|
|
$attribs = get_dev_attribs($alert['device_id']); |
86
|
|
|
|
87
|
|
|
$obj['hostname'] = $device['hostname']; |
88
|
|
|
$obj['sysName'] = $device['sysName']; |
89
|
|
|
$obj['sysDescr'] = $device['sysDescr']; |
90
|
|
|
$obj['sysContact'] = $device['sysContact']; |
91
|
|
|
$obj['os'] = $device['os']; |
92
|
|
|
$obj['type'] = $device['type']; |
93
|
|
|
$obj['ip'] = inet6_ntop($device['ip']); |
94
|
|
|
$obj['hardware'] = $device['hardware']; |
95
|
|
|
$obj['version'] = $device['version']; |
96
|
|
|
$obj['location'] = $device['location']; |
97
|
|
|
$obj['uptime'] = $device['uptime']; |
98
|
|
|
$obj['uptime_short'] = Time::formatInterval($device['uptime'], 'short'); |
99
|
|
|
$obj['uptime_long'] = Time::formatInterval($device['uptime']); |
100
|
|
|
$obj['description'] = $device['purpose']; |
101
|
|
|
$obj['notes'] = $device['notes']; |
102
|
|
|
$obj['alert_notes'] = $alert['note']; |
103
|
|
|
$obj['device_id'] = $alert['device_id']; |
104
|
|
|
$obj['rule_id'] = $alert['rule_id']; |
105
|
|
|
$obj['status'] = $device['status']; |
106
|
|
|
$obj['status_reason'] = $device['status_reason']; |
107
|
|
|
if (can_ping_device($attribs)) { |
108
|
|
|
$ping_stats = DevicePerf::where('device_id', $alert['device_id'])->latest('timestamp')->first(); |
109
|
|
|
$obj['ping_timestamp'] = $ping_stats->template; |
|
|
|
|
110
|
|
|
$obj['ping_loss'] = $ping_stats->loss; |
111
|
|
|
$obj['ping_min'] = $ping_stats->min; |
112
|
|
|
$obj['ping_max'] = $ping_stats->max; |
113
|
|
|
$obj['ping_avg'] = $ping_stats->avg; |
114
|
|
|
$obj['debug'] = json_decode($ping_stats->debug, true); |
115
|
|
|
} |
116
|
|
|
$extra = $alert['details']; |
117
|
|
|
|
118
|
|
|
$tpl = new Template; |
119
|
|
|
$template = $tpl->getTemplate($obj); |
120
|
|
|
|
121
|
|
|
if ($alert['state'] >= 1) { |
122
|
|
|
$obj['title'] = $template->title ?: 'Alert for device '.$device['hostname'].' - '.($alert['name'] ? $alert['name'] : $alert['rule']); |
123
|
|
|
if ($alert['state'] == 2) { |
124
|
|
|
$obj['title'] .= ' got acknowledged'; |
125
|
|
|
} elseif ($alert['state'] == 3) { |
126
|
|
|
$obj['title'] .= ' got worse'; |
127
|
|
|
} elseif ($alert['state'] == 4) { |
128
|
|
|
$obj['title'] .= ' got better'; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
foreach ($extra['rule'] as $incident) { |
132
|
|
|
$i++; |
133
|
|
|
$obj['faults'][$i] = $incident; |
134
|
|
|
$obj['faults'][$i]['string'] = null; |
135
|
|
|
foreach ($incident as $k => $v) { |
136
|
|
|
if (!empty($v) && $k != 'device_id' && (stristr($k, 'id') || stristr($k, 'desc') || stristr($k, 'msg')) && substr_count($k, '_') <= 1) { |
137
|
|
|
$obj['faults'][$i]['string'] .= $k.' = '.$v.'; '; |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
$obj['elapsed'] = $this->timeFormat(time() - strtotime($alert['time_logged'])); |
142
|
|
|
if (!empty($extra['diff'])) { |
143
|
|
|
$obj['diff'] = $extra['diff']; |
144
|
|
|
} |
145
|
|
|
} elseif ($alert['state'] == 0) { |
146
|
|
|
// Alert is now cleared |
147
|
|
|
$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'])); |
148
|
|
|
if (empty($id['id'])) { |
149
|
|
|
return false; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
$extra = []; |
153
|
|
|
if (!empty($id['details'])) { |
154
|
|
|
$extra = json_decode(gzuncompress($id['details']), true); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
// Reset count to 0 so alerts will continue |
158
|
|
|
$extra['count'] = 0; |
159
|
|
|
dbUpdate(array('details' => gzcompress(json_encode($id['details']), 9)), 'alert_log', 'id = ?', array($alert['id'])); |
160
|
|
|
|
161
|
|
|
$obj['title'] = $template->title_rec ?: 'Device '.$device['hostname'].' recovered from '.($alert['name'] ? $alert['name'] : $alert['rule']); |
162
|
|
|
$obj['elapsed'] = $this->timeFormat(strtotime($alert['time_logged']) - strtotime($id['time_logged'])); |
163
|
|
|
$obj['id'] = $id['id']; |
164
|
|
|
foreach ($extra['rule'] as $incident) { |
165
|
|
|
$i++; |
166
|
|
|
$obj['faults'][$i] = $incident; |
167
|
|
|
foreach ($incident as $k => $v) { |
168
|
|
|
if (!empty($v) && $k != 'device_id' && (stristr($k, 'id') || stristr($k, 'desc') || stristr($k, 'msg')) && substr_count($k, '_') <= 1) { |
169
|
|
|
$obj['faults'][$i]['string'] .= $k.' => '.$v.'; '; |
170
|
|
|
} |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
} else { |
174
|
|
|
return 'Unknown State'; |
|
|
|
|
175
|
|
|
}//end if |
176
|
|
|
$obj['builder'] = $alert['builder']; |
177
|
|
|
$obj['uid'] = $alert['id']; |
178
|
|
|
$obj['alert_id'] = $alert['alert_id']; |
179
|
|
|
$obj['severity'] = $alert['severity']; |
180
|
|
|
$obj['rule'] = $alert['rule']; |
181
|
|
|
$obj['name'] = $alert['name']; |
182
|
|
|
$obj['timestamp'] = $alert['time_logged']; |
183
|
|
|
$obj['contacts'] = $extra['contacts']; |
184
|
|
|
$obj['state'] = $alert['state']; |
185
|
|
|
$obj['template'] = $template; |
186
|
|
|
return $obj; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Format Elapsed Time |
191
|
|
|
* @param integer $secs Seconds elapsed |
192
|
|
|
* @return string |
193
|
|
|
*/ |
194
|
|
|
public function timeFormat($secs) |
195
|
|
|
{ |
196
|
|
|
$bit = array( |
197
|
|
|
'y' => $secs / 31556926 % 12, |
198
|
|
|
'w' => $secs / 604800 % 52, |
199
|
|
|
'd' => $secs / 86400 % 7, |
200
|
|
|
'h' => $secs / 3600 % 24, |
201
|
|
|
'm' => $secs / 60 % 60, |
202
|
|
|
's' => $secs % 60, |
203
|
|
|
); |
204
|
|
|
$ret = array(); |
205
|
|
|
foreach ($bit as $k => $v) { |
206
|
|
|
if ($v > 0) { |
207
|
|
|
$ret[] = $v.$k; |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
if (empty($ret)) { |
212
|
|
|
return 'none'; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
return join(' ', $ret); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
public function clearStaleAlerts() |
219
|
|
|
{ |
220
|
|
|
$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"; |
221
|
|
|
foreach (dbFetchRows($sql) as $alert) { |
222
|
|
|
if (empty($alert['hostname']) && isset($alert['alert_id'])) { |
223
|
|
|
dbDelete('alerts', '`id` = ?', array($alert['alert_id'])); |
224
|
|
|
echo "Stale-alert: #{$alert['alert_id']}" . PHP_EOL; |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Re-Validate Rule-Mappings |
231
|
|
|
* @param integer $device_id Device-ID |
232
|
|
|
* @param integer $rule Rule-ID |
233
|
|
|
* @return boolean |
234
|
|
|
*/ |
235
|
|
|
public function isRuleValid($device_id, $rule) |
236
|
|
|
{ |
237
|
|
|
global $rulescache; |
|
|
|
|
238
|
|
|
if (empty($rulescache[$device_id]) || !isset($rulescache[$device_id])) { |
239
|
|
|
foreach (AlertUtil::GetRules($device_id) as $chk) { |
240
|
|
|
$rulescache[$device_id][$chk['id']] = true; |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
if ($rulescache[$device_id][$rule] === true) { |
245
|
|
|
return true; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
return false; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Issue Alert-Object |
254
|
|
|
* @param array $alert |
255
|
|
|
* @return boolean |
256
|
|
|
*/ |
257
|
|
|
public function issueAlert($alert) |
258
|
|
|
{ |
259
|
|
|
if (dbFetchCell('SELECT attrib_value FROM devices_attribs WHERE attrib_type = "disable_notify" && device_id = ?', array($alert['device_id'])) == '1') { |
260
|
|
|
return true; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
if (Config::get('alert.fixed-contacts') == false) { |
264
|
|
|
if (empty($alert['query'])) { |
265
|
|
|
$alert['query'] = AlertDB::genSQL($alert['rule'], $alert['builder']); |
266
|
|
|
} |
267
|
|
|
$sql = $alert['query']; |
268
|
|
|
$qry = dbFetchRows($sql, array($alert['device_id'])); |
269
|
|
|
$alert['details']['contacts'] = AlertUtil::GetContacts($qry); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
$obj = $this->describeAlert($alert); |
273
|
|
|
if (is_array($obj)) { |
|
|
|
|
274
|
|
|
echo 'Issuing Alert-UID #'.$alert['id'].'/'.$alert['state'].':' . PHP_EOL; |
275
|
|
|
$this->extTransports($obj); |
276
|
|
|
|
277
|
|
|
echo "\r\n"; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
return true; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Issue ACK notification |
286
|
|
|
* @return void |
287
|
|
|
*/ |
288
|
|
|
public function runAcks() |
289
|
|
|
{ |
290
|
|
|
|
291
|
|
|
foreach ($this->loadAlerts('alerts.state = 2 && alerts.open = 1') as $alert) { |
292
|
|
|
$this->issueAlert($alert); |
293
|
|
|
dbUpdate(array('open' => 0), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id'])); |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Run Follow-Up alerts |
299
|
|
|
* @return void |
300
|
|
|
*/ |
301
|
|
|
public function runFollowUp() |
302
|
|
|
{ |
303
|
|
|
foreach ($this->loadAlerts('alerts.state > 0 && alerts.open = 0') as $alert) { |
304
|
|
|
if ($alert['state'] != 2 || ($alert['info']['until_clear'] === false)) { |
305
|
|
|
$rextra = json_decode($alert['extra'], true); |
306
|
|
|
if ($rextra['invert']) { |
307
|
|
|
continue; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
if (empty($alert['query'])) { |
311
|
|
|
$alert['query'] = AlertDB::genSQL($alert['rule'], $alert['builder']); |
312
|
|
|
} |
313
|
|
|
$chk = dbFetchRows($alert['query'], array($alert['device_id'])); |
314
|
|
|
//make sure we can json_encode all the datas later |
315
|
|
|
$cnt = count($chk); |
316
|
|
|
for ($i = 0; $i < $cnt; $i++) { |
317
|
|
|
if (isset($chk[$i]['ip'])) { |
318
|
|
|
$chk[$i]['ip'] = inet6_ntop($chk[$i]['ip']); |
319
|
|
|
} |
320
|
|
|
} |
321
|
|
|
$o = sizeof($alert['details']['rule']); |
322
|
|
|
$n = sizeof($chk); |
323
|
|
|
$ret = 'Alert #' . $alert['id']; |
324
|
|
|
$state = 0; |
325
|
|
|
if ($n > $o) { |
326
|
|
|
$ret .= ' Worsens'; |
327
|
|
|
$state = 3; |
328
|
|
|
$alert['details']['diff'] = array_diff($chk, $alert['details']['rule']); |
329
|
|
|
} elseif ($n < $o) { |
330
|
|
|
$ret .= ' Betters'; |
331
|
|
|
$state = 4; |
332
|
|
|
$alert['details']['diff'] = array_diff($alert['details']['rule'], $chk); |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
if ($state > 0 && $n > 0) { |
336
|
|
|
$alert['details']['rule'] = $chk; |
337
|
|
|
if (dbInsert(array( |
338
|
|
|
'state' => $state, |
339
|
|
|
'device_id' => $alert['device_id'], |
340
|
|
|
'rule_id' => $alert['rule_id'], |
341
|
|
|
'details' => gzcompress(json_encode($alert['details']), 9) |
342
|
|
|
), 'alert_log')) { |
343
|
|
|
dbUpdate(array('state' => $state, 'open' => 1, 'alerted' => 1), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id'])); |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
echo $ret . ' (' . $o . '/' . $n . ")\r\n"; |
347
|
|
|
} |
348
|
|
|
} |
349
|
|
|
} |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
public function loadAlerts($where) |
353
|
|
|
{ |
354
|
|
|
$alerts = []; |
355
|
|
|
foreach (dbFetchRows("SELECT alerts.id, alerts.device_id, alerts.rule_id, alerts.state, alerts.note, alerts.info FROM alerts WHERE $where") as $alert_status) { |
356
|
|
|
$alert = dbFetchRow( |
357
|
|
|
'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,alert_rules.query,alert_rules.builder 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', |
358
|
|
|
array($alert_status['device_id'], $alert_status['rule_id']) |
359
|
|
|
); |
360
|
|
|
|
361
|
|
|
if (empty($alert['rule_id']) || !$this->isRuleValid($alert_status['device_id'], $alert_status['rule_id'])) { |
362
|
|
|
echo 'Stale-Rule: #' . $alert_status['rule_id'] . '/' . $alert_status['device_id'] . "\r\n"; |
363
|
|
|
// Alert-Rule does not exist anymore, let's remove the alert-state. |
364
|
|
|
dbDelete('alerts', 'rule_id = ? && device_id = ?', [$alert_status['rule_id'], $alert_status['device_id']]); |
365
|
|
|
} else { |
366
|
|
|
$alert['alert_id'] = $alert_status['id']; |
367
|
|
|
$alert['state'] = $alert_status['state']; |
368
|
|
|
$alert['note'] = $alert_status['note']; |
369
|
|
|
if (!empty($alert['details'])) { |
370
|
|
|
$alert['details'] = json_decode(gzuncompress($alert['details']), true); |
371
|
|
|
} |
372
|
|
|
$alert['info'] = json_decode($alert_status['info'], true); |
373
|
|
|
$alerts[] = $alert; |
374
|
|
|
} |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
return $alerts; |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Run all alerts |
382
|
|
|
* @return void |
383
|
|
|
*/ |
384
|
|
|
public function runAlerts() |
|
|
|
|
385
|
|
|
{ |
386
|
|
|
foreach ($this->loadAlerts('alerts.state != 2 && alerts.open = 1') as $alert) { |
387
|
|
|
$noiss = false; |
388
|
|
|
$noacc = false; |
389
|
|
|
$updet = false; |
390
|
|
|
$rextra = json_decode($alert['extra'], true); |
391
|
|
|
if (!isset($rextra['recovery'])) { |
392
|
|
|
// backwards compatibility check |
393
|
|
|
$rextra['recovery'] = true; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
$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'])); |
397
|
|
|
|
398
|
|
|
if ($chk['alerted'] == $alert['state']) { |
399
|
|
|
$noiss = true; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
$tolerence_window = Config::get('alert.tolerance_window'); |
403
|
|
|
if (!empty($rextra['count']) && empty($rextra['interval'])) { |
404
|
|
|
// This check below is for compat-reasons |
405
|
|
|
if (!empty($rextra['delay'])) { |
406
|
|
|
if ((time() - strtotime($alert['time_logged']) + $tolerence_window) < $rextra['delay'] || (!empty($alert['details']['delay']) && (time() - $alert['details']['delay'] + $tolerence_window) < $rextra['delay'])) { |
407
|
|
|
continue; |
408
|
|
|
} else { |
409
|
|
|
$alert['details']['delay'] = time(); |
410
|
|
|
$updet = true; |
411
|
|
|
} |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
if ($alert['state'] == 1 && !empty($rextra['count']) && ($rextra['count'] == -1 || $alert['details']['count']++ < $rextra['count'])) { |
415
|
|
|
if ($alert['details']['count'] < $rextra['count']) { |
416
|
|
|
$noacc = true; |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
$updet = true; |
420
|
|
|
$noiss = false; |
421
|
|
|
} |
422
|
|
|
} else { |
423
|
|
|
// This is the new way |
424
|
|
|
if (!empty($rextra['delay']) && (time() - strtotime($alert['time_logged']) + $tolerence_window) < $rextra['delay']) { |
425
|
|
|
continue; |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
if (!empty($rextra['interval'])) { |
429
|
|
|
if (!empty($alert['details']['interval']) && (time() - $alert['details']['interval'] + $tolerence_window) < $rextra['interval']) { |
430
|
|
|
continue; |
431
|
|
|
} else { |
432
|
|
|
$alert['details']['interval'] = time(); |
433
|
|
|
$updet = true; |
434
|
|
|
} |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
if (in_array($alert['state'], [1,3,4]) && !empty($rextra['count']) && ($rextra['count'] == -1 || $alert['details']['count']++ < $rextra['count'])) { |
438
|
|
|
if ($alert['details']['count'] < $rextra['count']) { |
439
|
|
|
$noacc = true; |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
$updet = true; |
443
|
|
|
$noiss = false; |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
if ($chk['ignore'] == 1 || $chk['disabled'] == 1) { |
447
|
|
|
$noiss = true; |
448
|
|
|
$updet = false; |
449
|
|
|
$noacc = false; |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
if (AlertUtil::IsMaintenance($alert['device_id']) > 0) { |
453
|
|
|
$noiss = true; |
454
|
|
|
$noacc = true; |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
if ($updet) { |
458
|
|
|
dbUpdate(array('details' => gzcompress(json_encode($alert['details']), 9)), 'alert_log', 'id = ?', array($alert['id'])); |
459
|
|
|
} |
460
|
|
|
|
461
|
|
|
if (!empty($rextra['mute'])) { |
462
|
|
|
echo 'Muted Alert-UID #'.$alert['id']."\r\n"; |
463
|
|
|
$noiss = true; |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
if ($this->isParentDown($alert['device_id'])) { |
467
|
|
|
$noiss = true; |
468
|
|
|
Log::event('Skipped alerts because all parent devices are down', $alert['device_id'], 'alert', 1); |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
if ($alert['state'] == 0 && $rextra['recovery'] == false) { |
472
|
|
|
// Rule is set to not send a recovery alert |
473
|
|
|
$noiss = true; |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
if (!$noiss) { |
477
|
|
|
$this->issueAlert($alert); |
478
|
|
|
dbUpdate(array('alerted' => $alert['state']), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id'])); |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
if (!$noacc) { |
482
|
|
|
dbUpdate(array('open' => 0), 'alerts', 'rule_id = ? && device_id = ?', array($alert['rule_id'], $alert['device_id'])); |
483
|
|
|
} |
484
|
|
|
} |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
|
488
|
|
|
/** |
489
|
|
|
* Run external transports |
490
|
|
|
* @param array $obj Alert-Array |
491
|
|
|
* @return void |
492
|
|
|
*/ |
493
|
|
|
public function extTransports($obj) |
494
|
|
|
{ |
495
|
|
|
$type = new Template; |
496
|
|
|
|
497
|
|
|
// If alert transport mapping exists, override the default transports |
498
|
|
|
$transport_maps = AlertUtil::getAlertTransports($obj['alert_id']); |
499
|
|
|
|
500
|
|
|
if (!$transport_maps) { |
501
|
|
|
$transport_maps = AlertUtil::getDefaultAlertTransports(); |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
// alerting for default contacts, etc |
505
|
|
|
if (Config::get('alert.transports.mail') === true && !empty($obj['contacts'])) { |
506
|
|
|
$transport_maps[] = [ |
507
|
|
|
'transport_id' => null, |
508
|
|
|
'transport_type' => 'mail', |
509
|
|
|
'opts' => $obj, |
510
|
|
|
]; |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
foreach ($transport_maps as $item) { |
514
|
|
|
$class = 'LibreNMS\\Alert\\Transport\\'.ucfirst($item['transport_type']); |
515
|
|
|
if (class_exists($class)) { |
516
|
|
|
//FIXME remove Deprecated transport |
517
|
|
|
$transport_title = "Transport {$item['transport_type']}"; |
518
|
|
|
$obj['transport'] = $item['transport_type']; |
519
|
|
|
$obj['transport_name'] = $item['transport_name']; |
520
|
|
|
$obj['alert'] = new AlertData($obj); |
521
|
|
|
$obj['title'] = $type->getTitle($obj); |
522
|
|
|
$obj['alert']['title'] = $obj['title']; |
523
|
|
|
$obj['msg'] = $type->getBody($obj); |
524
|
|
|
c_echo(" :: $transport_title => "); |
525
|
|
|
$instance = new $class($item['transport_id']); |
526
|
|
|
$tmp = $instance->deliverAlert($obj, $item['opts']); |
527
|
|
|
$this->alertLog($tmp, $obj, $obj['transport']); |
528
|
|
|
unset($instance); |
529
|
|
|
echo PHP_EOL; |
530
|
|
|
} |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
if (count($transport_maps) === 0) { |
534
|
|
|
echo 'No configured transports'; |
535
|
|
|
} |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
// Log alert event |
539
|
|
|
public function alertLog($result, $obj, $transport) |
540
|
|
|
{ |
541
|
|
|
$prefix = [ |
542
|
|
|
0 => "recovery", |
543
|
|
|
1 => $obj['severity']." alert", |
544
|
|
|
2 => "acknowledgment" |
545
|
|
|
]; |
546
|
|
|
$prefix[3] = &$prefix[0]; |
547
|
|
|
$prefix[4] = &$prefix[0]; |
548
|
|
|
if ($result === true) { |
549
|
|
|
echo 'OK'; |
550
|
|
|
Log::event('Issued ' . $prefix[$obj['state']] . " for rule '" . $obj['name'] . "' to transport '" . $transport . "'", $obj['device_id'], 'alert', 1); |
551
|
|
|
} elseif ($result === false) { |
552
|
|
|
echo 'ERROR'; |
553
|
|
|
Log::event('Could not issue ' . $prefix[$obj['state']] . " for rule '" . $obj['name'] . "' to transport '" . $transport . "'", $obj['device_id'], null, 5); |
554
|
|
|
} else { |
555
|
|
|
echo "ERROR: $result\r\n"; |
556
|
|
|
Log::event('Could not issue ' . $prefix[$obj['state']] . " for rule '" . $obj['name'] . "' to transport '" . $transport . "' Error: " . $result, $obj['device_id'], 'error', 5); |
557
|
|
|
} |
558
|
|
|
return; |
559
|
|
|
} |
560
|
|
|
|
561
|
|
|
/** |
562
|
|
|
* Check if a device's all parent are down |
563
|
|
|
* Returns true if all parents are down |
564
|
|
|
* @param int $device Device-ID |
565
|
|
|
* @return bool |
566
|
|
|
*/ |
567
|
|
|
public function isParentDown($device) |
568
|
|
|
{ |
569
|
|
|
$parent_count = dbFetchCell("SELECT count(*) from `device_relationships` WHERE `child_device_id` = ?", array($device)); |
570
|
|
|
if (!$parent_count) { |
571
|
|
|
return false; |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
|
575
|
|
|
$down_parent_count = dbFetchCell("SELECT count(*) from devices as d LEFT JOIN devices_attribs as a ON d.device_id=a.device_id LEFT JOIN device_relationships as r ON d.device_id=r.parent_device_id WHERE d.status=0 AND d.ignore=0 AND d.disabled=0 AND r.child_device_id=? AND (d.status_reason='icmp' OR (a.attrib_type='override_icmp_disable' AND a.attrib_value=true))", array($device)); |
576
|
|
|
if ($down_parent_count == $parent_count) { |
577
|
|
|
return true; |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
return false; |
581
|
|
|
} |
582
|
|
|
} |
583
|
|
|
|
Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.