1<?php 2/** 3 * AlertUtil.php 4 * 5 * Extending the built in logging to add an event logger function 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program. If not, see <https://www.gnu.org/licenses/>. 19 * 20 * @link https://www.librenms.org 21 * @copyright 2019 KanREN, Inc. 22 * @author Heath Barnhart <hbarnhart@kanren.net> 23 */ 24 25namespace LibreNMS\Alert; 26 27use App\Models\Device; 28use App\Models\User; 29use DeviceCache; 30use LibreNMS\Config; 31use PHPMailer\PHPMailer\PHPMailer; 32 33class AlertUtil 34{ 35 /** 36 * Get the rule_id for a specific alert 37 * 38 * @param int $alert_id 39 * @return mixed|null 40 */ 41 private static function getRuleId($alert_id) 42 { 43 $query = 'SELECT `rule_id` FROM `alerts` WHERE `id`=?'; 44 45 return dbFetchCell($query, [$alert_id]); 46 } 47 48 /** 49 * Get the transport for a given alert_id 50 * 51 * @param int $alert_id 52 * @return array 53 */ 54 public static function getAlertTransports($alert_id) 55 { 56 $query = "SELECT b.transport_id, b.transport_type, b.transport_name FROM alert_transport_map AS a LEFT JOIN alert_transports AS b ON b.transport_id=a.transport_or_group_id WHERE a.target_type='single' AND a.rule_id=? UNION DISTINCT SELECT d.transport_id, d.transport_type, d.transport_name FROM alert_transport_map AS a LEFT JOIN alert_transport_groups AS b ON a.transport_or_group_id=b.transport_group_id LEFT JOIN transport_group_transport AS c ON b.transport_group_id=c.transport_group_id LEFT JOIN alert_transports AS d ON c.transport_id=d.transport_id WHERE a.target_type='group' AND a.rule_id=?"; 57 $rule_id = self::getRuleId($alert_id); 58 59 return dbFetchRows($query, [$rule_id, $rule_id]); 60 } 61 62 /** 63 * Returns the default transports 64 * 65 * @return array 66 */ 67 public static function getDefaultAlertTransports() 68 { 69 $query = 'SELECT transport_id, transport_type, transport_name FROM alert_transports WHERE is_default=true'; 70 71 return dbFetchRows($query); 72 } 73 74 /** 75 * Find contacts for alert 76 * @param array $results Rule-Result 77 * @return array 78 */ 79 public static function getContacts($results) 80 { 81 if (empty($results)) { 82 return []; 83 } 84 if (Config::get('alert.default_only') === true || Config::get('alerts.email.default_only') === true) { 85 $email = Config::get('alert.default_mail', Config::get('alerts.email.default')); 86 87 return $email ? [$email => ''] : []; 88 } 89 $users = User::query()->thisAuth()->get(); 90 $contacts = []; 91 $uids = []; 92 foreach ($results as $result) { 93 $tmp = null; 94 if (is_numeric($result['bill_id'])) { 95 $tmpa = dbFetchRows('SELECT user_id FROM bill_perms WHERE bill_id = ?', [$result['bill_id']]); 96 foreach ($tmpa as $tmp) { 97 $uids[$tmp['user_id']] = $tmp['user_id']; 98 } 99 } 100 if (is_numeric($result['port_id'])) { 101 $tmpa = dbFetchRows('SELECT user_id FROM ports_perms WHERE port_id = ?', [$result['port_id']]); 102 foreach ($tmpa as $tmp) { 103 $uids[$tmp['user_id']] = $tmp['user_id']; 104 } 105 } 106 if (is_numeric($result['device_id'])) { 107 if (Config::get('alert.syscontact') == true) { 108 if (dbFetchCell("SELECT attrib_value FROM devices_attribs WHERE attrib_type = 'override_sysContact_bool' AND device_id = ?", [$result['device_id']])) { 109 $tmpa = dbFetchCell("SELECT attrib_value FROM devices_attribs WHERE attrib_type = 'override_sysContact_string' AND device_id = ?", [$result['device_id']]); 110 } else { 111 $tmpa = dbFetchCell('SELECT sysContact FROM devices WHERE device_id = ?', [$result['device_id']]); 112 } 113 if (! empty($tmpa)) { 114 $contacts[$tmpa] = ''; 115 } 116 } 117 $tmpa = dbFetchRows('SELECT user_id FROM devices_perms WHERE device_id = ?', [$result['device_id']]); 118 foreach ($tmpa as $tmp) { 119 $uids[$tmp['user_id']] = $tmp['user_id']; 120 } 121 } 122 } 123 foreach ($users as $user) { 124 if (empty($user['email'])) { 125 continue; // no email, skip this user 126 } 127 if (empty($user['realname'])) { 128 $user['realname'] = $user['username']; 129 } 130 if (Config::get('alert.globals') && ($user['level'] >= 5 && $user['level'] < 10)) { 131 $contacts[$user['email']] = $user['realname']; 132 } elseif (Config::get('alert.admins') && $user['level'] == 10) { 133 $contacts[$user['email']] = $user['realname']; 134 } elseif (Config::get('alert.users') == true && in_array($user['user_id'], $uids)) { 135 $contacts[$user['email']] = $user['realname']; 136 } 137 } 138 139 $tmp_contacts = []; 140 foreach ($contacts as $email => $name) { 141 if (strstr($email, ',')) { 142 $split_contacts = preg_split('/[,\s]+/', $email); 143 foreach ($split_contacts as $split_email) { 144 if (! empty($split_email)) { 145 $tmp_contacts[$split_email] = $name; 146 } 147 } 148 } else { 149 $tmp_contacts[$email] = $name; 150 } 151 } 152 153 if (! empty($tmp_contacts)) { 154 // Validate contacts so we can fall back to default if configured. 155 $mail = new PHPMailer(); 156 foreach ($tmp_contacts as $tmp_email => $tmp_name) { 157 if ($mail->validateAddress($tmp_email) != true) { 158 unset($tmp_contacts[$tmp_email]); 159 } 160 } 161 } 162 163 // Copy all email alerts to default contact if configured. 164 $default_mail = Config::get('alert.default_mail'); 165 if (! isset($tmp_contacts[$default_mail]) && Config::get('alert.default_copy')) { 166 $tmp_contacts[$default_mail] = ''; 167 } 168 // Send email to default contact if no other contact found 169 if (empty($tmp_contacts) && Config::get('alert.default_if_none') && $default_mail) { 170 $tmp_contacts[$default_mail] = ''; 171 } 172 173 return $tmp_contacts; 174 } 175 176 public static function getRules($device_id) 177 { 178 $query = 'SELECT DISTINCT a.* FROM alert_rules a 179 LEFT JOIN alert_device_map d ON a.id=d.rule_id AND (a.invert_map = 0 OR a.invert_map = 1 AND d.device_id = ?) 180 LEFT JOIN alert_group_map g ON a.id=g.rule_id AND (a.invert_map = 0 OR a.invert_map = 1 AND g.group_id IN (SELECT DISTINCT device_group_id FROM device_group_device WHERE device_id = ?)) 181 LEFT JOIN alert_location_map l ON a.id=l.rule_id AND (a.invert_map = 0 OR a.invert_map = 1 AND l.location_id IN (SELECT DISTINCT location_id FROM devices WHERE device_id = ?)) 182 LEFT JOIN device_group_device dg ON g.group_id=dg.device_group_id AND dg.device_id = ? 183 WHERE a.disabled = 0 AND ( 184 (d.device_id IS NULL AND g.group_id IS NULL) 185 OR (a.invert_map = 0 AND (d.device_id=? OR dg.device_id=?)) 186 OR (a.invert_map = 1 AND (d.device_id != ? OR d.device_id IS NULL) AND (dg.device_id != ? OR dg.device_id IS NULL)) 187 )'; 188 189 $params = [$device_id, $device_id, $device_id, $device_id, $device_id, $device_id, $device_id, $device_id]; 190 191 return dbFetchRows($query, $params); 192 } 193 194 /** 195 * Check if device is under maintenance 196 * @param int $device_id Device-ID 197 * @return bool 198 */ 199 public static function isMaintenance($device_id) 200 { 201 return DeviceCache::get($device_id)->isUnderMaintenance(); 202 } 203 204 /** 205 * Check if device is set to ignore alerts 206 * @param int $device_id Device-ID 207 * @return bool 208 */ 209 public static function hasDisableNotify($device_id) 210 { 211 $device = Device::find($device_id); 212 213 return ! is_null($device) && $device->disable_notify; 214 } 215 216 /** 217 * Process Macros 218 * @param string $rule Rule to process 219 * @param int $x Recursion-Anchor 220 * @return string|bool 221 */ 222 public static function runMacros($rule, $x = 1) 223 { 224 $macros = Config::get('alert.macros.rule', []); 225 krsort($macros); 226 foreach ($macros as $macro => $value) { 227 if (! strstr($macro, ' ')) { 228 $rule = str_replace('%macros.' . $macro, '(' . $value . ')', $rule); 229 } 230 } 231 if (strstr($rule, '%macros.')) { 232 if (++$x < 30) { 233 $rule = self::runMacros($rule, $x); 234 } else { 235 return false; 236 } 237 } 238 239 return $rule; 240 } 241} 242