1 // +------------------------------------------------------------------+
2 // |             ____ _               _        __  __ _  __           |
3 // |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
4 // |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
5 // |           | |___| | | |  __/ (__|   <    | |  | | . \            |
6 // |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
7 // |                                                                  |
8 // | Copyright Mathias Kettner 2014             mk@mathias-kettner.de |
9 // +------------------------------------------------------------------+
10 //
11 // This file is part of Check_MK.
12 // The official homepage is at http://mathias-kettner.de/check_mk.
13 //
14 // check_mk is free software;  you can redistribute it and/or modify it
15 // under the  terms of the  GNU General Public License  as published by
16 // the Free Software Foundation in version 2.  check_mk is  distributed
17 // in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
18 // out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
19 // PARTICULAR PURPOSE. See the  GNU General Public License for more de-
20 // ails.  You should have  received  a copy of the  GNU  General Public
21 // License along with GNU Make; see the file  COPYING.  If  not,  write
22 // to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
23 // Boston, MA 02110-1301 USA.
24 
25 #include "LogEntry.h"
26 #include <stdlib.h>
27 #include <string.h>
28 #include "strutil.h"
29 
LogEntry(unsigned lineno,char * line)30 LogEntry::LogEntry(unsigned lineno, char *line) {
31     // zero all elements as fast as possible -> default values
32     bzero(this, sizeof(LogEntry));
33     _lineno = lineno;
34 
35     // make a copy of the message and strip trailing newline
36     _msg = strdup(line);
37     _msglen = strlen(line);
38     while (_msglen > 0 && _msg[_msglen - 1] == '\n') {
39         _msg[--_msglen] = '\0';
40 
41         // keep unsplitted copy of the message (needs lots of memory,
42         // maybe we could optimize that one day...)
43     }
44     _complete = strdup(_msg);
45 
46     // pointer to options (everything after ':')
47     _options = _complete;
48     while ((*_options != 0) && *_options != ':') {
49         _options++;
50     }
51     if (*_options != 0)  // line contains colon
52     {
53         _options++;  // skip ':'
54         while (*_options == ' ') {
55             _options++;  // skip space after ':'
56         }
57     }
58 
59     // [1260722267] xxx - extract timestamp, validate message
60     if (_msglen < 13 || _msg[0] != '[' || _msg[11] != ']') {
61         _logclass = LOGCLASS_INVALID;
62         return;  // ignore invalid lines silently
63     }
64     _msg[11] = 0;  // zero-terminate time stamp
65     _time = atoi(_msg + 1);
66     _text = _msg + 13;  // also skip space after timestamp
67 
68     // now classify the log message. Some messages
69     // refer to other table, some do not.
70     if (handleStatusEntry() || handleNotificationEntry() ||
71         handlePassiveCheckEntry() || handleExternalCommandEntry()) {
72         updateReferences();
73     } else {
74         handleTextEntry() || handleProgrammEntry();  // Performance killer
75                                                      // strstr in
76                                                      // handleProgrammEntry!
77     }
78     // rest is LOGCLASS_INFO
79 }
80 
~LogEntry()81 LogEntry::~LogEntry() {
82     free(_msg);
83     free(_complete);
84 }
85 
handleStatusEntry()86 bool LogEntry::handleStatusEntry() {
87     //// TODO: check if its worth of implementing
88     //// Most lines are status entries anyway...
89     //    int len_text = strlen(_text);
90     //    if (len_text < 12)
91     //        return false;
92     //
93     //    // Quick basic check
94     //    // We can skip all other strcmp() calls
95     //    // if the first letter does not match
96     //    switch (_text[0]) {
97     //        case 'I':
98     //        case 'C':
99     //        case 'S':
100     //        case 'T':
101     //        case 'H':
102     //            break;
103     //        default:
104     //            return false;
105     //    }
106 
107     // HOST states
108     if ((strncmp(_text, "INITIAL HOST STATE: ", 20) == 0) ||
109         (strncmp(_text, "CURRENT HOST STATE: ", 20) == 0) ||
110         (strncmp(_text, "HOST ALERT: ", 12) == 0)) {
111         if (_text[0] == 'H') {
112             _logclass = LOGCLASS_ALERT;
113             _type = ALERT_HOST;
114         } else if (_text[0] == 'I') {
115             _logclass = LOGCLASS_STATE;
116             _type = STATE_HOST_INITIAL;
117         } else {
118             _logclass = LOGCLASS_STATE;
119             _type = STATE_HOST;
120         }
121 
122         char *scan = _text;
123         _text = next_token(&scan, ':');
124         scan++;
125 
126         _host_name = next_token(&scan, ';');
127         _state = hostStateToInt(safe_next_token(&scan, ';'));
128         _state_type = next_token(&scan, ';');
129         _attempt = atoi(safe_next_token(&scan, ';'));
130         _check_output = next_token(&scan, ';');
131         return true;
132     }
133     if (strncmp(_text, "HOST DOWNTIME ALERT: ", 21) == 0) {
134         _logclass = LOGCLASS_ALERT;
135         _type = DOWNTIME_ALERT_HOST;
136         char *scan = _text;
137         _text = next_token(&scan, ':');
138         scan++;
139 
140         _host_name = next_token(&scan, ';');
141         _state_type = next_token(&scan, ';');
142         _comment = next_token(&scan, ';');
143         return true;
144     }
145     if (strncmp(_text, "HOST ACKNOWLEDGE ALERT: ", 24) == 0) {
146         _logclass = LOGCLASS_ALERT;
147         _type = ACKNOWLEDGE_ALERT_HOST;
148         char *scan = _text;
149         _text = next_token(&scan, ':');
150         scan++;
151 
152         _host_name = next_token(&scan, ';');
153         _state_type = next_token(&scan, ';');
154         _contact_name = next_token(&scan, ';');
155         _comment = next_token(&scan, ';');
156         return true;
157     }
158     if (strncmp(_text, "HOST FLAPPING ALERT: ", 21) == 0) {
159         _logclass = LOGCLASS_ALERT;
160         _type = FLAPPING_HOST;
161         char *scan = _text;
162         _text = next_token(&scan, ':');
163         scan++;
164 
165         _host_name = next_token(&scan, ';');
166         _state_type = next_token(&scan, ';');
167         _comment = next_token(&scan, ';');
168         return true;
169     }
170 
171     // SERVICE states
172     if ((strncmp(_text, "INITIAL SERVICE STATE: ", 23) == 0) ||
173         (strncmp(_text, "CURRENT SERVICE STATE: ", 23) == 0) ||
174         (strncmp(_text, "SERVICE ALERT: ", 15) == 0)) {
175         if (_text[0] == 'S') {
176             _logclass = LOGCLASS_ALERT;
177             _type = ALERT_SERVICE;
178         } else if (_text[0] == 'I') {
179             _logclass = LOGCLASS_STATE;
180             _type = STATE_SERVICE_INITIAL;
181         } else {
182             _logclass = LOGCLASS_STATE;
183             _type = STATE_SERVICE;
184         }
185         char *scan = _text;
186         _text = next_token(&scan, ':');
187         scan++;
188 
189         _host_name = next_token(&scan, ';');
190         _svc_desc = next_token(&scan, ';');
191         _state = serviceStateToInt(safe_next_token(&scan, ';'));
192         _state_type = next_token(&scan, ';');
193         _attempt = atoi(safe_next_token(&scan, ';'));
194         _check_output = next_token(&scan, ';');
195         return true;
196     }
197 
198     if (strncmp(_text, "SERVICE DOWNTIME ALERT: ", 24) == 0) {
199         _logclass = LOGCLASS_ALERT;
200         _type = DOWNTIME_ALERT_SERVICE;
201         char *scan = _text;
202         _text = next_token(&scan, ':');
203         scan++;
204 
205         _host_name = next_token(&scan, ';');
206         _svc_desc = next_token(&scan, ';');
207         _state_type = next_token(&scan, ';');
208         _comment = next_token(&scan, ';');
209         return true;
210     }
211 
212     if (strncmp(_text, "SERVICE ACKNOWLEDGE ALERT: ", 27) == 0) {
213         _logclass = LOGCLASS_ALERT;
214         _type = ACKNOWLEDGE_ALERT_SERVICE;
215         char *scan = _text;
216         _text = next_token(&scan, ':');
217         scan++;
218 
219         _host_name = next_token(&scan, ';');
220         _svc_desc = next_token(&scan, ';');
221         _state_type = next_token(&scan, ';');
222         _contact_name = next_token(&scan, ';');
223         _comment = next_token(&scan, ';');
224         return true;
225     }
226 
227     if (strncmp(_text, "SERVICE FLAPPING ALERT: ", 24) == 0) {
228         _logclass = LOGCLASS_ALERT;
229         _type = FLAPPING_SERVICE;
230         char *scan = _text;
231         _text = next_token(&scan, ':');
232         scan++;
233 
234         _host_name = next_token(&scan, ';');
235         _svc_desc = next_token(&scan, ';');
236         _state_type = next_token(&scan, ';');
237         _comment = next_token(&scan, ';');
238         return true;
239     }
240 
241     if (strncmp(_text, "TIMEPERIOD TRANSITION: ", 23) == 0) {
242         _logclass = LOGCLASS_STATE;
243         _type = TIMEPERIOD_TRANSITION;
244         return true;
245     }
246 
247     return false;
248 }
249 
250 // Examples of host notifications. Beware CUSTOM and DOWNTIME notifications
251 // have a different column order. This can be considered as a bug. But we
252 // need to parse that anyway.
253 // HOST NOTIFICATION: omdadmin;localhost;check-mk-notify;DOWNTIMESTOPPED (UP);mk
254 // HOST NOTIFICATION: omdadmin;localhost;CUSTOM (UP);check-mk-notify;OK -
255 // 127.0.0.1: rta 0.055ms, lost 0%;omdadmin;TEST
256 // HOST NOTIFICATION: omdadmin;localhost;DOWN;check-mk-notify;Manually set to
257 // Down by omdadmin
handleNotificationEntry()258 bool LogEntry::handleNotificationEntry() {
259     if ((strncmp(_text, "HOST NOTIFICATION: ", 19) == 0) ||
260         (strncmp(_text, "SERVICE NOTIFICATION: ", 22) == 0)) {
261         _logclass = LOGCLASS_NOTIFICATION;
262         bool svc = _text[0] == 'S';
263         char *scan = _text;
264         _text = next_token(&scan, ':');
265         scan++;
266 
267         _contact_name = next_token(&scan, ';');
268         _host_name = next_token(&scan, ';');
269         if (svc) {
270             _svc_desc = next_token(&scan, ';');
271         }
272 
273         _state_type = safe_next_token(&scan, ';');
274         _command_name = next_token(&scan, ';');
275 
276         if (svc) {
277             _state = serviceStateToInt(_state_type);
278         } else {
279             _state = hostStateToInt(_state_type);
280         }
281 
282         // If that state is not parsable then we assume that the order
283         // is swapped
284         if ((svc && _state == 4) || (!svc && _state == 3)) {
285             const char *swap = _state_type;
286             _state_type = _command_name;
287             _command_name = swap;
288             if (svc) {
289                 _state = serviceStateToInt(_state_type);
290             } else {
291                 _state = hostStateToInt(_state_type);
292             }
293         }
294 
295         _check_output = next_token(&scan, ';');
296         return true;
297     }
298 
299     return false;
300 }
301 
handlePassiveCheckEntry()302 bool LogEntry::handlePassiveCheckEntry() {
303     if ((strncmp(_text, "PASSIVE SERVICE CHECK: ", 23) == 0) ||
304         (strncmp(_text, "PASSIVE HOST CHECK: ", 20) == 0)) {
305         _logclass = LOGCLASS_PASSIVECHECK;
306         bool svc = _text[8] == 'S';
307         char *scan = _text;
308         _text = next_token(&scan, ':');
309         scan++;
310 
311         _host_name = next_token(&scan, ';');
312         if (svc) {
313             _svc_desc = next_token(&scan, ';');
314         }
315         _state = atoi(safe_next_token(&scan, ';'));
316         _check_output = next_token(&scan, ';');
317         return true;
318     }
319 
320     return false;
321 }
322 
handleExternalCommandEntry()323 bool LogEntry::handleExternalCommandEntry() {
324     if (strncmp(_text, "EXTERNAL COMMAND:", 17) == 0) {
325         _logclass = LOGCLASS_COMMAND;
326         char *scan = _text;
327         _text = next_token(&scan, ':');
328         return true;  // TODO(mk): join with host/service information?
329         /* Damit wir die restlichen Spalten ordentlich befuellen, braeuchten
330            wir eine komplette Liste von allen external commands und
331            deren Parameteraufbau. Oder gibt es hier auch eine bessere
332            Loesung? */
333     }
334     return false;
335 }
336 
handleTextEntry()337 bool LogEntry::handleTextEntry() {
338     if (strncmp(_text, "LOG VERSION: 2.0", 16) == 0) {
339         _logclass = LOGCLASS_PROGRAM;
340         _type = LOG_VERSION;
341         return true;
342     }
343     if ((strncmp(_text, "logging initial states", 22) == 0) ||
344         (strncmp(_text, "logging intitial states", 23) == 0)) {
345         _logclass = LOGCLASS_PROGRAM;
346         _type = LOG_INITIAL_STATES;
347         return true;
348     }
349     return false;
350 }
351 
handleProgrammEntry()352 bool LogEntry::handleProgrammEntry() {
353     if ((strstr(_text, "starting...") != nullptr) ||
354         (strstr(_text, "active mode...") != nullptr)) {
355         _logclass = LOGCLASS_PROGRAM;
356         _type = CORE_STARTING;
357         return true;
358     }
359     if ((strstr(_text, "shutting down...") != nullptr) ||
360         (strstr(_text, "Bailing out") != nullptr) ||
361         (strstr(_text, "standby mode...") != nullptr)) {
362         _logclass = LOGCLASS_PROGRAM;
363         _type = CORE_STOPPING;
364         return true;
365     }
366     if (strstr(_text, "restarting...") != nullptr) {
367         _logclass = LOGCLASS_PROGRAM;
368         return true;
369     }
370     return false;
371 }
372 
serviceStateToInt(const char * s)373 int LogEntry::serviceStateToInt(const char *s) {
374     if (s == nullptr) {
375         return 3;  // can happen at garbled log line
376     }
377 
378     const char *last = s + strlen(s) - 1;
379     if (*last == ')') {
380         last--;
381     }
382 
383     // WARN, CRITICAL, OK, UNKNOWN, RECOVERY
384     switch (*last) {
385         case 'K':
386             return 0;
387         case 'Y':
388             return 0;
389         case 'G':
390             return 1;
391         case 'L':
392             return 2;
393         case 'N':
394             return 3;
395         default:
396             return 4;
397     }
398 }
399 
hostStateToInt(const char * s)400 int LogEntry::hostStateToInt(const char *s) {
401     if (s == nullptr) {
402         return 2;  // can happen at garbled log line
403     }
404 
405     const char *last = s + strlen(s) - 1;
406     if (*last == ')') {  // handle CUSTOM (UP) and DOWNTIMESTOPPED (DOWN)
407         last--;
408     }
409 
410     // UP, DOWN, UNREACHABLE, RECOVERY
411     switch (*last) {
412         case 'P':
413             return 0;
414         case 'Y':
415             return 0;
416         case 'N':
417             return 1;
418         case 'E':
419             return 2;
420         default:
421             return 3;
422     }
423 }
424 
updateReferences()425 unsigned LogEntry::updateReferences() {
426     unsigned updated = 0;
427     if (_host_name != nullptr) {
428         _host = find_host(_host_name);
429         updated++;
430     }
431     if (_svc_desc != nullptr) {
432         _service = find_service(_host_name, _svc_desc);
433         updated++;
434     }
435     if (_contact_name != nullptr) {
436         _contact = find_contact(_contact_name);
437         updated++;
438     }
439     if (_command_name != nullptr) {
440         // Older Nagios headers are not const-correct... :-P
441         _command = find_command(const_cast<char *>(_command_name));
442         updated++;
443     }
444     return updated;
445 }
446