1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* Copyright (C) 2015 Daniel Preussker <[email protected]> |
4
|
|
|
* This program is free software: you can redistribute it and/or modify |
5
|
|
|
* it under the terms of the GNU General Public License as published by |
6
|
|
|
* the Free Software Foundation, either version 3 of the License, or |
7
|
|
|
* (at your option) any later version. |
8
|
|
|
* |
9
|
|
|
* This program is distributed in the hope that it will be useful, |
10
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
11
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the |
12
|
|
|
* GNU General Public License for more details. |
13
|
|
|
* |
14
|
|
|
* You should have received a copy of the GNU General Public License |
15
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Device-Grouping |
20
|
|
|
* @author Daniel Preussker <[email protected]> |
21
|
|
|
* @author Tony Murray <[email protected]> |
22
|
|
|
* @copyright 2016 f0o, murrant, LibreNMS |
23
|
|
|
* @license GPL |
24
|
|
|
* @package LibreNMS |
25
|
|
|
* @subpackage Devices |
26
|
|
|
*/ |
27
|
|
|
|
28
|
|
|
use LibreNMS\Config; |
|
|
|
|
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Add a new device group |
32
|
|
|
* @param $pattern |
33
|
|
|
* @param $name |
34
|
|
|
* @param $desc |
35
|
|
|
* @return int|string |
36
|
|
|
*/ |
37
|
|
|
function AddDeviceGroup($name, $desc, $pattern) |
38
|
|
|
{ |
39
|
|
|
$group_id = dbInsert(array('name' => $name, 'desc' => $desc, 'pattern' => $pattern), 'device_groups'); |
40
|
|
|
if ($group_id) { |
|
|
|
|
41
|
|
|
UpdateDeviceGroup($group_id); |
42
|
|
|
} |
43
|
|
|
return $group_id; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Update a device group |
48
|
|
|
* @param $group_id |
49
|
|
|
* @param $pattern |
50
|
|
|
* @param $name |
51
|
|
|
* @param $desc |
52
|
|
|
* @return bool |
53
|
|
|
*/ |
54
|
|
|
function EditDeviceGroup($group_id, $name = null, $desc = null, $pattern = null) |
55
|
|
|
{ |
56
|
|
|
$vars = array(); |
57
|
|
|
if (!is_null($name)) { |
58
|
|
|
$vars['name'] = $name; |
59
|
|
|
} |
60
|
|
|
if (!is_null($desc)) { |
61
|
|
|
$vars['desc'] = $desc; |
62
|
|
|
} |
63
|
|
|
if (!is_null($pattern)) { |
64
|
|
|
$vars['pattern'] = $pattern; |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
$success = dbUpdate($vars, 'device_groups', 'id=?', array($group_id)) >= 0; |
68
|
|
|
|
69
|
|
|
if ($success) { |
70
|
|
|
UpdateDeviceGroup($group_id); |
71
|
|
|
} |
72
|
|
|
return $success; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Generate SQL from Group-Pattern |
77
|
|
|
* @param string $pattern Pattern to generate SQL for |
78
|
|
|
* @param string $search What to searchid for |
79
|
|
|
* @param int $extra |
80
|
|
|
* @return string sql to perform the search |
81
|
|
|
*/ |
82
|
|
|
function GenGroupSQL($pattern, $search = '', $extra = 0) |
83
|
|
|
{ |
84
|
|
|
$pattern = RunGroupMacros($pattern); |
85
|
|
|
if ($pattern === false) { |
86
|
|
|
return false; |
|
|
|
|
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
if (starts_with($pattern, '%')) { |
90
|
|
|
// v1 pattern |
91
|
|
|
$tables = array(); |
92
|
|
|
$words = explode(' ', $pattern); |
93
|
|
|
foreach ($words as $word) { |
94
|
|
|
if (starts_with($word, '%') && str_contains($word, '.')) { |
95
|
|
|
list($table, $column) = explode('.', $word, 2); |
96
|
|
|
$table = str_replace('%', '', $table); |
97
|
|
|
$tables[] = mres(str_replace('(', '', $table)); |
98
|
|
|
$pattern = str_replace($word, $table . '.' . $column, $pattern); |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
$tables = array_keys(array_flip($tables)); |
102
|
|
|
} else { |
103
|
|
|
// v2 pattern |
104
|
|
|
$tables = getTablesFromPattern($pattern); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
$pattern = rtrim($pattern, '&|'); |
108
|
|
|
|
109
|
|
|
if ($tables[0] != 'devices' && dbFetchCell('SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME = ?', array($tables[0],'device_id')) != 1) { |
110
|
|
|
//Our first table has no valid glue, prepend the 'devices' table to it! |
111
|
|
|
array_unshift($tables, 'devices'); |
112
|
|
|
$tables = array_unique($tables); // remove devices from later in the array if it exists |
113
|
|
|
} |
114
|
|
|
$x = sizeof($tables)-1; |
115
|
|
|
$i = 0; |
116
|
|
|
$join = ""; |
117
|
|
|
while ($i < $x) { |
118
|
|
|
if (isset($tables[$i+1])) { |
119
|
|
|
$gtmp = ResolveGlues(array($tables[$i+1]), 'device_id'); |
120
|
|
|
if ($gtmp === false) { |
121
|
|
|
//Cannot resolve glue-chain. Rule is invalid. |
122
|
|
|
return false; |
|
|
|
|
123
|
|
|
} |
124
|
|
|
$last = ""; |
125
|
|
|
$qry = ""; |
126
|
|
|
foreach ($gtmp as $glue) { |
127
|
|
|
if (empty($last)) { |
128
|
|
|
list($tmp,$last) = explode('.', $glue); |
129
|
|
|
$qry .= $glue.' = '; |
130
|
|
|
} else { |
131
|
|
|
$parts = explode('.', $glue); |
132
|
|
|
if (count($parts) == 3) { |
133
|
|
|
list($tmp, $new, $last) = $parts; |
134
|
|
|
} else { |
135
|
|
|
list($tmp,$new) = $parts; |
136
|
|
|
} |
137
|
|
|
$qry .= $tmp.'.'.$last.' && '.$tmp.'.'.$new.' = '; |
138
|
|
|
$last = $new; |
139
|
|
|
} |
140
|
|
|
if (!in_array($tmp, $tables)) { |
141
|
|
|
$tables[] = $tmp; |
142
|
|
|
} |
143
|
|
|
} |
144
|
|
|
$join .= "( ".$qry.$tables[0].".device_id ) && "; |
145
|
|
|
} |
146
|
|
|
$i++; |
147
|
|
|
} |
148
|
|
|
if ($extra === 1) { |
149
|
|
|
$sql_extra = ",`devices`.*"; |
150
|
|
|
} |
151
|
|
|
if (!empty($search)) { |
152
|
|
|
$search = str_replace("(", "", $tables[0]).'.'.$search. ' AND'; |
153
|
|
|
} |
154
|
|
|
if (!empty($join)) { |
|
|
|
|
155
|
|
|
$join = '('.rtrim($join, '& ').') &&'; |
156
|
|
|
} |
157
|
|
|
$sql = 'SELECT DISTINCT('.str_replace('(', '', $tables[0]).'.device_id)'.$sql_extra.' FROM '.implode(',', $tables).' WHERE '.$join.' '.$search.' ('.str_replace(array('%', '@', '!~', '~'), array('', '.*', 'NOT REGEXP', 'REGEXP'), $pattern).')'; |
|
|
|
|
158
|
|
|
return $sql; |
159
|
|
|
}//end GenGroupSQL() |
160
|
|
|
|
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Extract an array of tables in a pattern |
164
|
|
|
* |
165
|
|
|
* @param string $pattern |
166
|
|
|
* @return array |
167
|
|
|
*/ |
168
|
|
|
function getTablesFromPattern($pattern) |
169
|
|
|
{ |
170
|
|
|
preg_match_all('/[A-Za-z_]+(?=\.[A-Za-z_]+ )/', $pattern, $tables); |
171
|
|
|
if (is_null($tables)) { |
172
|
|
|
return array(); |
173
|
|
|
} |
174
|
|
|
return array_keys(array_flip($tables[0])); // unique tables only |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Run the group queries again to get fresh list of devices for this group |
179
|
|
|
* @param integer $group_id Group-ID |
180
|
|
|
* @return string |
181
|
|
|
*/ |
182
|
|
|
function QueryDevicesFromGroup($group_id) |
183
|
|
|
{ |
184
|
|
|
$group = dbFetchRow('SELECT pattern,params FROM device_groups WHERE id = ?', array($group_id)); |
185
|
|
|
$pattern = rtrim($group['pattern'], '&|'); |
186
|
|
|
$params = (array)json_decode($group['params']); |
187
|
|
|
if (!empty($pattern)) { |
188
|
|
|
$result = dbFetchColumn(GenGroupSQL($pattern), $params); |
189
|
|
|
return $result; |
|
|
|
|
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
return false; |
|
|
|
|
193
|
|
|
}//end QueryDevicesFromGroup() |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Get an array of all the device ids belonging to this group_id |
197
|
|
|
* @param $group_id |
198
|
|
|
* @param bool $nested Return an array of arrays containing 'device_id'. (for API compatibility) |
199
|
|
|
* @param string $full Return all fields from devices_id |
200
|
|
|
* @return array |
201
|
|
|
*/ |
202
|
|
|
function GetDevicesFromGroup($group_id, $nested = false, $full = '') |
203
|
|
|
{ |
204
|
|
|
if ($full) { |
205
|
|
|
$query = 'SELECT `device_groups`.`name`, `devices`.* FROM `device_groups` INNER JOIN `device_group_device` ON `device_groups`.`id` = `device_group_device`.`device_group_id` INNER JOIN `devices` ON `device_group_device`.`device_id` = `devices`.`device_id` WHERE `device_groups`.`id` = ?'; |
206
|
|
|
} else { |
207
|
|
|
$query = 'SELECT `device_id` FROM `device_group_device` WHERE `device_group_id` = ? '; |
208
|
|
|
} |
209
|
|
|
if ($nested) { |
210
|
|
|
return dbFetchRows($query, array($group_id)); |
211
|
|
|
} else { |
212
|
|
|
return dbFetchColumn($query, array($group_id)); |
213
|
|
|
} |
214
|
|
|
}//end GetDevicesFromGroup() |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Get all Device-Groups |
218
|
|
|
* @return array |
219
|
|
|
*/ |
220
|
|
|
function GetDeviceGroups() |
221
|
|
|
{ |
222
|
|
|
return dbFetchRows('SELECT * FROM device_groups ORDER BY name'); |
223
|
|
|
}//end GetDeviceGroups() |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Run the group queries again to get fresh list of groups for this device |
227
|
|
|
* @param integer $device_id Device-ID |
228
|
|
|
* @param int $extra Return extra info about the groups (name, desc, pattern) |
229
|
|
|
* @return array |
230
|
|
|
*/ |
231
|
|
|
function QueryGroupsFromDevice($device_id, $extra = 0) |
232
|
|
|
{ |
233
|
|
|
$ret = array(); |
234
|
|
|
foreach (GetDeviceGroups() as $group) { |
235
|
|
|
$params = (array)json_decode($group['params']); |
236
|
|
|
array_unshift($params, $device_id); |
237
|
|
|
if (dbFetchCell(GenGroupSQL($group['pattern'], 'device_id=?', $extra).' LIMIT 1', $params) == $device_id) { |
238
|
|
|
if ($extra === 0) { |
239
|
|
|
$ret[] = $group['id']; |
240
|
|
|
} else { |
241
|
|
|
$ret[] = $group; |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
return $ret; |
247
|
|
|
}//end QueryGroupsFromDevice() |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Get the Device Group IDs of a Device from the database |
251
|
|
|
* @param $device_id |
252
|
|
|
* @param int $extra Return extra info about the groups (name, desc, pattern) |
253
|
|
|
* @return array |
254
|
|
|
*/ |
255
|
|
|
function GetGroupsFromDevice($device_id, $extra = 0) |
256
|
|
|
{ |
257
|
|
|
$ret = array(); |
258
|
|
|
if ($extra === 0) { |
259
|
|
|
$ret = dbFetchColumn('SELECT `device_group_id` FROM `device_group_device` WHERE `device_id`=?', array($device_id)); |
260
|
|
|
} else { |
261
|
|
|
$ret = dbFetchRows('SELECT `device_groups`.* FROM `device_group_device` LEFT JOIN `device_groups` ON `device_group_device`.`device_group_id`=`device_groups`.`id` WHERE `device_group_device`.`device_id`=?', array($device_id)); |
262
|
|
|
} |
263
|
|
|
return $ret; |
264
|
|
|
}//end GetGroupsFromDeviceDB() |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* Process Macros |
268
|
|
|
* @param string $rule Rule to process |
269
|
|
|
* @param int $x Recursion-Anchor |
270
|
|
|
* @return string|boolean |
271
|
|
|
*/ |
272
|
|
|
function RunGroupMacros($rule, $x = 1) |
273
|
|
|
{ |
274
|
|
|
$macros = Config::get('alert.macros.group', []); |
275
|
|
|
|
276
|
|
|
krsort($macros); |
277
|
|
|
foreach ($macros as $macro => $value) { |
278
|
|
|
if (!strstr($macro, " ")) { |
279
|
|
|
$rule = str_replace('%macros.'.$macro, '('.$value.')', $rule); |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
if (strstr($rule, "%macros")) { |
283
|
|
|
if (++$x < 30) { |
284
|
|
|
$rule = RunGroupMacros($rule, $x); |
285
|
|
|
} else { |
286
|
|
|
return false; |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
return $rule; |
290
|
|
|
}//end RunGroupMacros() |
291
|
|
|
|
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Update device-group relationship for the given device id |
295
|
|
|
* @param $device_id |
296
|
|
|
*/ |
297
|
|
|
function UpdateGroupsForDevice($device_id) |
298
|
|
|
{ |
299
|
|
|
d_echo("### Start Device Groups ###\n"); |
300
|
|
|
$queried_groups = QueryGroupsFromDevice($device_id); |
301
|
|
|
$db_groups = GetGroupsFromDevice($device_id); |
302
|
|
|
|
303
|
|
|
// compare the arrays to get the added and removed groups |
304
|
|
|
$added_groups = array_diff($queried_groups, $db_groups); |
305
|
|
|
$removed_groups = array_diff($db_groups, $queried_groups); |
306
|
|
|
|
307
|
|
|
d_echo("Groups Added: ".implode(',', $added_groups).PHP_EOL); |
308
|
|
|
d_echo("Groups Removed: ".implode(',', $removed_groups).PHP_EOL); |
309
|
|
|
|
310
|
|
|
// insert new groups |
311
|
|
|
$insert = array(); |
312
|
|
|
foreach ($added_groups as $group_id) { |
313
|
|
|
$insert[] = array('device_id' => $device_id, 'device_group_id' => $group_id); |
314
|
|
|
} |
315
|
|
|
if (!empty($insert)) { |
316
|
|
|
dbBulkInsert($insert, 'device_group_device'); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
// remove old groups |
320
|
|
|
if (!empty($removed_groups)) { |
321
|
|
|
dbDelete('device_group_device', '`device_id`=? AND `device_group_id` IN ' . dbGenPlaceholders(count($removed_groups)), array_merge([$device_id], $removed_groups)); |
322
|
|
|
} |
323
|
|
|
d_echo("### End Device Groups ###\n"); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* Update the device-group relationship for the given group id |
328
|
|
|
* @param $group_id |
329
|
|
|
*/ |
330
|
|
|
function UpdateDeviceGroup($group_id) |
331
|
|
|
{ |
332
|
|
|
$queried_devices = QueryDevicesFromGroup($group_id); |
333
|
|
|
$db_devices = GetDevicesFromGroup($group_id); |
334
|
|
|
|
335
|
|
|
// compare the arrays to get the added and removed devices |
336
|
|
|
$added_devices = array_diff($queried_devices, $db_devices); |
|
|
|
|
337
|
|
|
$removed_devices = array_diff($db_devices, $queried_devices); |
|
|
|
|
338
|
|
|
|
339
|
|
|
// insert new devices |
340
|
|
|
$insert = array(); |
341
|
|
|
foreach ($added_devices as $device_id) { |
342
|
|
|
$insert[] = array('device_id' => $device_id, 'device_group_id' => $group_id); |
343
|
|
|
} |
344
|
|
|
if (!empty($insert)) { |
345
|
|
|
dbBulkInsert($insert, 'device_group_device'); |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
// remove old devices |
349
|
|
|
if (!empty($removed_devices)) { |
350
|
|
|
dbDelete('device_group_device', '`device_group_id`=? AND `device_id` IN ' . dbGenPlaceholders(count($removed_devices)), array_merge([$group_id], $removed_devices)); |
351
|
|
|
} |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Fill in params into the pattern, replacing placeholders (?) |
356
|
|
|
* If $params is empty or null, just returns $pattern |
357
|
|
|
* |
358
|
|
|
* @return string |
359
|
|
|
*/ |
360
|
|
|
function formatDeviceGroupPattern($pattern, $params) |
361
|
|
|
{ |
362
|
|
|
// fill in parameters |
363
|
|
|
foreach ((array)$params as $value) { |
364
|
|
|
if (!is_numeric($value) && !starts_with($value, "'")) { |
365
|
|
|
$value = "'".$value."'"; |
366
|
|
|
} |
367
|
|
|
$pattern = preg_replace('/\?/', $value, $pattern, 1); |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
return $pattern; |
371
|
|
|
} |
372
|
|
|
|
Let?s assume that you have a directory layout like this:
and let?s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: