Issues (2963)

includes/services.inc.php (1 issue)

1
<?php
2
3
use App\Models\Device;
4
use LibreNMS\Config;
5
use LibreNMS\RRD\RrdDefinition;
6
7
function get_service_status($device = null)
8
{
9
    $sql_query = 'SELECT service_status, count(service_status) as count FROM services WHERE';
10
    $sql_param = [];
11
    $add = 0;
12
13
    if (! is_null($device)) {
14
        // Add a device filter to the SQL query.
15
        $sql_query .= ' `device_id` = ?';
16
        $sql_param[] = $device;
17
        $add++;
18
    }
19
20
    if ($add == 0) {
21
        // No filters, remove " WHERE" -6
22
        $sql_query = substr($sql_query, 0, strlen($sql_query) - 6);
23
    }
24
    $sql_query .= ' GROUP BY service_status';
25
26
    // $service is not null, get only what we want.
27
    $result = dbFetchRows($sql_query, $sql_param);
28
29
    // Set our defaults to 0
30
    $service_count = [0 => 0, 1 => 0, 2 => 0];
31
    // Rebuild the array in a more convenient method
32
    foreach ($result as $v) {
33
        $service_count[$v['service_status']] = $v['count'];
34
    }
35
36
    return $service_count;
37
}
38
39
function add_service($device, $type, $desc, $ip = '', $param = '', $ignore = 0, $disabled = 0, $template_id = '', $name)
40
{
41
    if (! is_array($device)) {
42
        $device = device_by_id_cache($device);
43
    }
44
45
    if (empty($ip)) {
46
        $ip = Device::pollerTarget($device['hostname']);
47
    }
48
49
    $insert = ['device_id' => $device['device_id'], 'service_ip' => $ip, 'service_type' => $type, 'service_changed' => ['UNIX_TIMESTAMP(NOW())'], 'service_desc' => $desc, 'service_param' => $param, 'service_ignore' => $ignore, 'service_status' => 3, 'service_message' => 'Service not yet checked', 'service_ds' => '{}', 'service_disabled' => $disabled, 'service_template_id' => $template_id, 'service_name' => $name];
50
51
    return dbInsert($insert, 'services');
52
}
53
54
function service_get($device = null, $service = null)
55
{
56
    $sql_query = 'SELECT `service_id`,`device_id`,`service_ip`,`service_type`,`service_desc`,`service_param`,`service_ignore`,`service_status`,`service_changed`,`service_message`,`service_disabled`,`service_ds`,`service_template_id`,`service_name` FROM `services` WHERE';
57
    $sql_param = [];
58
    $add = 0;
59
60
    d_echo('SQL Query: ' . $sql_query);
61
    if (! is_null($service)) {
62
        // Add a service filter to the SQL query.
63
        $sql_query .= ' `service_id` = ? AND';
64
        $sql_param[] = $service;
65
        $add++;
66
    }
67
    if (! is_null($device)) {
68
        // Add a device filter to the SQL query.
69
        $sql_query .= ' `device_id` = ? AND';
70
        $sql_param[] = $device;
71
        $add++;
72
    }
73
74
    if ($add == 0) {
75
        // No filters, remove " WHERE" -6
76
        $sql_query = substr($sql_query, 0, strlen($sql_query) - 6);
77
    } else {
78
        // We have filters, remove " AND" -4
79
        $sql_query = substr($sql_query, 0, strlen($sql_query) - 4);
80
    }
81
    d_echo('SQL Query: ' . $sql_query);
82
83
    // $service is not null, get only what we want.
84
    $services = dbFetchRows($sql_query, $sql_param);
85
    d_echo('Service Array: ' . print_r($services, true) . "\n");
86
87
    return $services;
88
}
89
90
function edit_service($update = [], $service = null)
91
{
92
    if (! is_numeric($service)) {
93
        return false;
94
    }
95
96
    return dbUpdate($update, 'services', '`service_id`=?', [$service]);
97
}
98
99
function delete_service($service = null)
100
{
101
    if (! is_numeric($service)) {
102
        return false;
103
    }
104
105
    return dbDelete('services', '`service_id` =  ?', [$service]);
106
}
107
108
function discover_service($device, $service)
109
{
110
    if (! dbFetchCell('SELECT COUNT(service_id) FROM `services` WHERE `service_type`= ? AND `device_id` = ?', [$service, $device['device_id']])) {
111
        add_service($device, $service, "$service Monitoring (Auto Discovered)", null, null, 0, 0, 0, "AUTO: $service");
112
        log_event('Autodiscovered service: type ' . $service, $device, 'service', 2);
113
        echo '+';
114
    }
115
    echo "$service ";
116
}
117
118
function poll_service($service)
119
{
120
    $update = [];
121
    $old_status = $service['service_status'];
122
    $check_cmd = '';
123
124
    // if we have a script for this check, use it.
125
    $check_script = Config::get('install_dir') . '/includes/services/check_' . strtolower($service['service_type']) . '.inc.php';
126
    if (is_file($check_script)) {
127
        include $check_script;
128
    }
129
130
    // If we do not have a cmd from the check script, build one.
131
    if ($check_cmd == '') {
0 ignored issues
show
The condition $check_cmd == '' is always true.
Loading history...
132
        $check_cmd = Config::get('nagios_plugins') . '/check_' . $service['service_type'] . ' -H ' . ($service['service_ip'] ? $service['service_ip'] : $service['hostname']);
133
        $check_cmd .= ' ' . $service['service_param'];
134
    }
135
136
    $service_id = $service['service_id'];
137
    // Some debugging
138
    d_echo("\nNagios Service - $service_id\n");
139
    // the check_service function runs $check_cmd through escapeshellcmd, so
140
    [$new_status, $msg, $perf] = check_service($check_cmd);
141
    d_echo("Response: $msg\n");
142
143
    // If we have performance data we will store it.
144
    if (count($perf) > 0) {
145
        // Yes, We have perf data.
146
        $rrd_name = ['services', $service_id];
147
148
        // Set the DS in the DB if it is blank.
149
        $DS = [];
150
        foreach ($perf as $k => $v) {
151
            $DS[$k] = $v['uom'];
152
        }
153
        d_echo('Service DS: ' . json_encode($DS, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n");
154
        if (($service['service_ds'] == '{}') || ($service['service_ds'] == '')) {
155
            $update['service_ds'] = json_encode($DS);
156
        }
157
158
        // rrd definition
159
        $rrd_def = new RrdDefinition();
160
        foreach ($perf as $k => $v) {
161
            if (($v['uom'] == 'c') && ! (preg_match('/[Uu]ptime/', $k))) {
162
                // This is a counter, create the DS as such
163
                $rrd_def->addDataset($k, 'COUNTER', 0);
164
            } else {
165
                // Not a counter, must be a gauge
166
                $rrd_def->addDataset($k, 'GAUGE', 0);
167
            }
168
        }
169
170
        // Update data
171
        $fields = [];
172
        foreach ($perf as $k => $v) {
173
            $fields[$k] = $v['value'];
174
        }
175
176
        $tags = compact('service_id', 'rrd_name', 'rrd_def');
177
        //TODO not sure if we have $device at this point, if we do replace faked $device
178
        data_update(['hostname' => $service['hostname']], 'services', $tags, $fields);
179
    }
180
181
    if ($old_status != $new_status) {
182
        // Status has changed, update.
183
        $update['service_changed'] = time();
184
        $update['service_status'] = $new_status;
185
        $update['service_message'] = $msg;
186
187
        // TODO: Put the 3 lines below in a function getStatus(int) ?
188
        $status_text = [0 => 'OK', 1 => 'Warning', 3 => 'Unknown'];
189
        $old_status_text = isset($status_text[$old_status]) ? $status_text[$old_status] : 'Critical';
190
        $new_status_text = isset($status_text[$new_status]) ? $status_text[$new_status] : 'Critical';
191
192
        log_event(
193
            "Service '{$service['service_type']}' changed status from $old_status_text to $new_status_text - {$service['service_desc']} - $msg",
194
            $service['device_id'],
195
            'service',
196
            4,
197
            $service['service_id']
198
        );
199
    }
200
201
    if ($service['service_message'] != $msg) {
202
        // Message has changed, update.
203
        $update['service_message'] = $msg;
204
    }
205
206
    if (count($update) > 0) {
207
        edit_service($update, $service['service_id']);
208
    }
209
210
    return true;
211
}
212
213
function check_service($command)
214
{
215
    // This array is used to test for valid UOM's to be used for graphing.
216
    // Valid values from: https://nagios-plugins.org/doc/guidelines.html#AEN200
217
    // Note: This array must be decend from 2 char to 1 char so that the search works correctly.
218
    $valid_uom = ['us', 'ms', 'KB', 'MB', 'GB', 'TB', 'c', 's', '%', 'B'];
219
220
    // Make our command safe.
221
    $parts = preg_split('~(?:\'[^\']*\'|"[^"]*")(*SKIP)(*F)|\h+~', trim($command));
222
    $safe_command = implode(' ', array_map(function ($part) {
223
        $trimmed = preg_replace('/^(\'(.*)\'|"(.*)")$/', '$2$3', $part);
224
225
        return escapeshellarg($trimmed);
226
    }, $parts));
227
228
    d_echo("Request:  $safe_command\n");
229
230
    // Run the command and return its response.
231
    exec('LC_NUMERIC="C" ' . $safe_command, $response_array, $status);
232
233
    // exec returns an array, lets implode it back to a string.
234
    $response_string = implode("\n", $response_array);
235
236
    // Split out the response and the performance data.
237
    [$response, $perf] = explode('|', $response_string);
238
239
    // Split each performance metric
240
    $perf_arr = explode(' ', $perf);
241
242
    // Create an array for our metrics.
243
    $metrics = [];
244
245
    // Loop through the perf string extracting our metric data
246
    foreach ($perf_arr as $string) {
247
        // Separate the DS and value: DS=value
248
        [$ds,$values] = explode('=', trim($string));
249
250
        // Keep the first value, discard the others.
251
        [$value,,,] = explode(';', trim($values));
252
        $value = trim($value);
253
254
        // Set an empty uom
255
        $uom = '';
256
257
        // is the UOM valid - https://nagios-plugins.org/doc/guidelines.html#AEN200
258
        foreach ($valid_uom as $v) {
259
            if ((strlen($value) - strlen($v)) === strpos($value, $v)) {
260
                // Yes, store and strip it off the value
261
                $uom = $v;
262
                $value = substr($value, 0, -strlen($v));
263
                break;
264
            }
265
        }
266
267
        if ($ds != '') {
268
            // Normalize ds for rrd : ds-name must be 1 to 19 characters long in the characters [a-zA-Z0-9_]
269
            // http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html
270
            $normalized_ds = preg_replace('/[^a-zA-Z0-9_]/', '', $ds);
271
            // if ds_name is longer than 19 characters, only use the first 19
272
            if (strlen($normalized_ds) > 19) {
273
                $normalized_ds = substr($normalized_ds, 0, 19);
274
                d_echo($ds . ' exceeded 19 characters, renaming to ' . $normalized_ds . "\n");
275
            }
276
            if ($ds != $normalized_ds) {
277
                // ds has changed. check if normalized_ds is already in the array
278
                if (isset($metrics[$normalized_ds])) {
279
                    d_echo($normalized_ds . " collides with an existing index\n");
280
                    $perf_unique = 0;
281
                    // Try to generate a unique name
282
                    for ($i = 0; $i < 10; $i++) {
283
                        $tmp_ds_name = substr($normalized_ds, 0, 18) . $i;
284
                        if (! isset($metrics[$tmp_ds_name])) {
285
                            d_echo($normalized_ds . " collides with an existing index\n");
286
                            $normalized_ds = $tmp_ds_name;
287
                            $perf_unique = 1;
288
                            break;
289
                        }
290
                    }
291
                    if ($perf_unique == 0) {
292
                        // Try harder to generate a unique name
293
                        for ($i = 0; $i < 10; $i++) {
294
                            for ($j = 0; $j < 10; $j++) {
295
                                $tmp_ds_name = substr($normalized_ds, 0, 17) . $j . $i;
296
                                if (! isset($perf[$tmp_ds_name])) {
297
                                    $normalized_ds = $tmp_ds_name;
298
                                    $perf_unique = 1;
299
                                    break 2;
300
                                }
301
                            }
302
                        }
303
                    }
304
                    if ($perf_unique == 0) {
305
                        d_echo('could not generate a unique ds-name for ' . $ds . "\n");
306
                    }
307
                }
308
                $ds = $normalized_ds;
309
            }
310
            // We have a DS. Add an entry to the array.
311
            d_echo('Perf Data - DS: ' . $ds . ', Value: ' . $value . ', UOM: ' . $uom . "\n");
312
            $metrics[$ds] = ['value'=>$value, 'uom'=>$uom];
313
        } else {
314
            // No DS. Don't add an entry to the array.
315
            d_echo("Perf Data - None.\n");
316
        }
317
    }
318
319
    return [$status, $response, $metrics];
320
}
321
322
/**
323
 * List all available services from nagios plugins directory
324
 *
325
 * @return array
326
 */
327
function list_available_services()
328
{
329
    return \LibreNMS\Services::list();
330
}
331