-
Notifications
You must be signed in to change notification settings - Fork 0
/
nrpe.pas
347 lines (312 loc) · 11.3 KB
/
nrpe.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
{ --------------------------------------------------------------------------
godaemon
NRPE handling unit
Copyright (c) Michael Nixon 2015.
Please see the LICENSE file for licensing information.
-------------------------------------------------------------------------- }
{ --------------------------------------------------------------------------
-------------------------------------------------------------------------- }
unit nrpe;
interface
uses baseunix, unix, unixutil, sockets, sysutils, classes;
const
StatusBufferSize = 1024;
MatchBufferSize = 64;
type
tmessagebuffer = array[0..0] of byte;
tNRPEMessageScraper = class(tobject)
private
statusBuffer: array[0..StatusBufferSize - 1] of byte;
statusBufferPtr: longint;
matchBuffer: array[0..MatchBufferSize - 1] of byte;
matchBufferLength: longint;
matchBufferPos: longint;
inMatch: boolean;
function ProcessStatusBuffer: boolean;
public
constructor Create(prefixString: ansistring);
function ParseBuffer(var buffer; size: longint): boolean;
end;
procedure SetNagiosStatus(statusID: longint; statusMessage: ansistring; timestamp: longint);
procedure SetNagiosStatusRaw(statusID: longint; statusMessage: ansistring; timestamp: longint);
function GetNagiosStatus(var statusID: longint; var statusMessage: ansistring; var timestamp: longint): boolean;
function ParseNagiosStatusString(status: ansistring; var statusID: longint; var statusMessage: ansistring): boolean;
function GetNagiosStatusString(statusID: longint; statusMessage: ansistring): ansistring;
procedure SetDefaultNagiosStatus;
function SafelyDeleteFile(filename: ansistring): boolean;
implementation
uses settings, btime;
{ --------------------------------------------------------------------------
Process a finished status buffer.
Returns TRUE if we could process it without errors.
Returns FALSE if we could not process it properly.
-------------------------------------------------------------------------- }
function tNRPEMessageScraper.ProcessStatusBuffer: boolean;
var
nagiosStatusID: longint;
nagiosStatusMessage: ansistring;
nagiosStatus: ansistring;
begin
setlength(nagiosStatus, self.statusBufferPtr);
move(self.statusBuffer[0], nagiosStatus[1], self.statusBufferPtr);
if ParseNagiosStatusString(nagiosStatus, nagiosStatusID, nagiosStatusMessage) then begin
_settings.daemonNRPEStatusTS := unixtimeint;
SetNagiosStatus(nagiosStatusID, nagiosStatusMessage, _settings.daemonNRPEStatusTS);
result := true;
end else begin
result := false;
end;
end;
{ --------------------------------------------------------------------------
tNRPEMessageScraper constructor
-------------------------------------------------------------------------- }
constructor tNRPEMessageScraper.Create(prefixString: ansistring);
const
funcname = 'tNRPEMessageScraper.Create: ';
var
matchString: ansistring;
begin
if length(prefixString) = 0 then begin
raise exception.create(funcname + 'Invalid prefix string');
exit;
end;
matchString := '&' + prefixString + '&{';
if length(matchString) > MatchBufferSize then begin
raise exception.create(funcname + 'Prefix string too long');
exit;
end;
move(matchString[1], self.matchBuffer[0], length(matchString));
self.matchBufferLength := length(matchString);
matchBufferPos := 0;
inMatch := false;
statusBufferPtr := 0;
end;
{ --------------------------------------------------------------------------
Parse a log buffer and look for NRPE messages.
Returns TRUE if we found at least one NRPE status update (otherwise FALSE)
-------------------------------------------------------------------------- }
function tNRPEMessageScraper.ParseBuffer(var buffer; size: longint): boolean;
const
CLOSING_BRACE = 125;
var
i: longint;
b: byte;
processed: boolean;
begin
processed := false;
for i := 0 to size - 1 do begin
b := tmessagebuffer(buffer)[i];
if self.inMatch then begin
{ If this is a closing brace, stop matching }
if b = CLOSING_BRACE then begin
self.inMatch := false;
processed := self.ProcessStatusBuffer;
self.statusBufferPtr := 0;
end else begin
{ Keep adding to message }
self.statusBuffer[self.statusBufferPtr] := b;
inc(self.statusBufferPtr);
if self.statusBufferPtr >= StatusBufferSize then begin
{ Status buffer full, have to cut off the message }
self.inMatch := false;
processed := self.ProcessStatusBuffer;
self.statusBufferPtr := 0;
end;
end;
end else begin
{ Matching }
if b = self.matchBuffer[self.matchBufferPos] then begin
inc(self.matchBufferPos);
if self.matchBufferPos >= self.matchBufferLength then begin
{ Got a full match }
self.inMatch := true;
self.matchBufferPos := 0;
end;
end else begin
{ Fail, reset matching }
self.matchBufferPos := 0;
end;
end;
end;
result := processed;
end;
{ --------------------------------------------------------------------------
Assuming you know <filename> exists for sure, try to delete it safely.
Returns TRUE on success, or FALSE if we could not.
-------------------------------------------------------------------------- }
function SafelyDeleteFile(filename: ansistring): boolean;
begin
try
DeleteFile(filename);
except
on e: exception do begin
result := false;
exit;
end;
end;
result := true;
end;
{ --------------------------------------------------------------------------
Set the nagios status to the default status.
-------------------------------------------------------------------------- }
procedure SetDefaultNagiosStatus;
begin
SetNagiosStatus(_settings.daemonNRPEDefaultStatusID,
_settings.daemonNRPEDefaultStatusString,
_settings.daemonNRPEStatusTS);
end;
{ --------------------------------------------------------------------------
Get a nagios status string (suitable for use with ParseNagiosStatusString).
Returns the nagios status string.
-------------------------------------------------------------------------- }
function GetNagiosStatusString(statusID: longint; statusMessage: ansistring): ansistring;
var
s: ansistring;
begin
case statusID of
_nagios_status_ok: begin
s := 'OK';
end;
_nagios_status_warning: begin
s := 'WARNING';
end;
_nagios_status_critical: begin
s := 'CRITICAL';
end;
_nagios_status_unknown: begin
s := 'UNKNOWN';
end;
end;
result := s + ':' + statusMessage;
end;
{ --------------------------------------------------------------------------
Parse a nagios status string of the format:
STATUS:message
Sets <statusID> and <statusMessage>.
Returns TRUE if the status could be parsed, or FALSE if it could not.
-------------------------------------------------------------------------- }
function ParseNagiosStatusString(status: ansistring; var statusID: longint; var statusMessage: ansistring): boolean;
var
statusIDStr: ansistring;
i: longint;
begin
result := false;
if length(status) < 3 then exit;
i := pos(':', status);
if (i < 2) or (i > (length(status) - 1)) then exit;
statusIDStr := uppercase(copy(status, 1, i - 1));
statusMessage := copy(status, i + 1, length(status) - i);
if statusIDStr = 'OK' then begin
statusID := _nagios_status_ok;
end else if statusIDStr = 'WARNING' then begin
statusID := _nagios_status_warning;
end else if statusIDStr = 'CRITICAL' then begin
statusID := _nagios_status_critical;
end else if statusIDStr = 'UNKNOWN' then begin
statusID := _nagios_status_unknown;
end else begin
{ Invalid status }
exit;
end;
result := true;
end;
{ --------------------------------------------------------------------------
Set the nagios status for the task (writes the status file).
<statusID> is the nagios status (_nagios_status_xxxxx)
<statusMessage> is the status message.
-------------------------------------------------------------------------- }
procedure SetNagiosStatusRaw(statusID: longint; statusMessage: ansistring; timestamp: longint);
const
funcname = 'SetNagiosStatus(): ';
var
statusTempFile: ansistring;
f: textfile;
begin
{ To do this "semi atomically", first create a temp file with the status }
statusTempFile := _settings.daemonStatusFilename + '.temp';
if fileexists(statusTempFile) then begin
SafelyDeleteFile(statusTempFile);
end;
filemode := fmOpenWrite;
assignfile(f, statusTempFile);
rewrite(f);
writeln(f, GetNagiosStatusString(statusID, statusMessage));
writeln(f, timestamp);
closefile(f);
{ Delete the old file, if present }
if fileexists(_settings.daemonStatusFilename) then begin
if not SafelyDeleteFile(_settings.daemonStatusFilename) then begin
{ Probably in use, so sleep a little bit }
sleep(250);
{ Now give it another try }
if not SafelyDeleteFile(_settings.daemonStatusFilename) then begin
{ Shouldn't happen, make a note of it and clean up }
SafelyDeleteFile(statusTempFile);
_logger.Log(funcname + 'Failed to remove old status file!');
exit;
end;
end;
end;
{ Rename new file to old filename }
if not RenameFile(statusTempFile, _settings.daemonStatusFilename) then begin
{ Failed, clean up }
SafelyDeleteFile(statusTempFile);
_logger.Log(funcname + 'Failed to rename new status file!');
exit;
end;
end;
{ --------------------------------------------------------------------------
Get the nagios status of the task (by reading the nagios status file).
Returns TRUE if we could get the status successfully.
-------------------------------------------------------------------------- }
function GetNagiosStatus(var statusID: longint; var statusMessage: ansistring; var timestamp: longint): boolean;
var
status: ansistring;
tsString: ansistring;
f: textfile;
begin
result := false;
if not fileexists(_settings.daemonStatusFilename) then exit;
try
filemode := fmOpenRead;
assignfile(f, _settings.daemonStatusFilename);
reset(f);
readln(f, status);
readln(f, tsString);
closefile(f);
timestamp := strtoint(tsString);
except
on e: exception do begin
{ Failed status as the file was unparseable }
exit;
end;
end;
result := ParseNagiosStatusString(status, statusID, statusMessage);
end;
{ --------------------------------------------------------------------------
This is the same as SetNagiosStatus, except it will include information in
the status about the failure count.
-------------------------------------------------------------------------- }
procedure SetNagiosStatus(statusID: longint; statusMessage: ansistring; timestamp: longint);
var
usedStatusID: longint;
begin
if not _settings.daemonNRPEAlertRecentlyFailed then begin
{ Just set raw status }
SetNagiosStatusRaw(statusID, statusMessage, timestamp);
end else begin
if not _settings.recentlyFailed then begin
{ Just set raw status }
SetNagiosStatusRaw(statusID, statusMessage, timestamp);
end else begin
{ Need to combine the 2 statuses }
usedStatusID := _nagios_status_warning;
if statusID = _nagios_status_critical then begin
usedStatusID := statusID;
end;
SetNagiosStatusRaw(usedStatusID, 'Task has been restarted recently: ' +
statusMessage, timestamp);
end;
end;
end;
end.