-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathenv_events.c
433 lines (375 loc) · 10.5 KB
/
env_events.c
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
/*
* Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers.
* This is free and unencumbered software released into the public domain.
*/
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "log.h"
#include "events.h"
#include "env_events.h"
#include "notifiers.h"
#include "str.h"
/*
* Environment events
*/
/* Regex params. */
#define MAX_MATCHES 32
/* Event match types. */
#define MATCH_TYPES_LEN 2
static const char *const match_types[] = {"substr", "regex"};
/* Environment events list. */
static int num_env_events;
struct env_event env_events[MAX_ENV_EVENTS] = {0};
/**
* Safe string-to-int routine that takes into account:
* - Overflow and Underflow
* - No undefined behavior
*
* Taken from https://stackoverflow.com/a/12923949/3594716
* and slightly adapted: no error classification, because
* I don't need to know, error is error.
*
* @param out Pointer to integer.
* @param s String to be converted.
*
* @return Returns 0 if success and a negative number otherwise.
*/
static int str2int(int *out, const char *s)
{
char *end;
if (s[0] == '\0' || isspace(s[0]))
return -1;
errno = 0;
long l = strtol(s, &end, 10);
/* Both checks are needed because INT_MAX == LONG_MAX is possible. */
if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX))
return -1;
if (l < INT_MIN || (errno == ERANGE && l == LONG_MIN))
return -1;
if (*end != '\0')
return -1;
*out = l;
return 0;
}
/**
* @brief Retrieves the event string from the environment variables.
*
* @param ev_num Event number.
* @param str String identifier.
*
* @return Returns the event string.
*/
static char *get_event_str(int ev_num, char *str)
{
char *env;
char ev[64] = {0};
snprintf(ev, sizeof ev - 1, "EVENT%d_%s", ev_num, str);
if (!(env = getenv(ev)))
panic("Unable to find event for %s\n", ev);
return env;
}
/**
* @brief Retrieves the index of the event from the environment variables.
*
* @param ev_num Event number.
* @param str String identifier.
* @param str_list List of strings to match against.
* @param size Size of the string list.
*
* @return Returns the index of the matching event.
*/
static int
get_event_idx(int ev_num, char *str, const char *const *str_list, int size)
{
char *env = get_event_str(ev_num, str);
for (int i = 0; i < size; i++) {
if (!strcmp(env, str_list[i]))
return i;
}
panic("String parameter (%s) invalid for %s\n", env, str);
}
/**
* @brief Handles match replacement in the event mask message.
*
* @param notif_message Pointer to the append buffer.
* @param c_msk Pointer to the current position in the mask message.
* @param e_msk End of the mask message.
* @param pmatch Array of regex matches.
* @param env Pointer to the environment event.
* @param log_ev Pointer to the log event.
*
* @return Returns 1 if the replacement was handled, 0 otherwise.
*/
static int handle_match_replacement(
struct str_ab *notif_message,
const char **c_msk, const char *e_msk,
regmatch_t *pmatch,
struct env_event *env,
struct log_event *log_ev)
{
const char *c = *c_msk;
const char *e = e_msk;
size_t match = *c - '0';
regoff_t off;
regoff_t len;
/* Check if there is a second digit. */
if (c < e) {
if (c[1] >= '0' && c[1] <= '9') {
match = (match * 10) + (c[1] - '0');
c++;
}
}
/* Validate if read number is within the match range
* i.e., between 1-nsub.
*/
if (!match || match > env->regex.re_nsub)
return 0;
*c_msk = c;
/* Append c_msk into our dst, according to the informed
* match.
*/
off = pmatch[match].rm_so;
len = pmatch[match].rm_eo - off;
if (ab_append_str(notif_message, log_ev->msg + off, len) < 0)
return 0;
return 1;
}
/**
* @brief Creates a masked message based on the base mask string
* and the matches found.
*
* @param env Pointer to the environment event.
* @param pmatch Array of regex matches.
* @param log_ev Pointer to the log event.
* @param buf Buffer to store the masked message.
* @param buf_size Size of the buffer.
*
* @return Returns the pointer to the end of the masked message.
*/
static int
create_masked_message(struct env_event *env, regmatch_t *pmatch,
struct log_event *log_ev, struct str_ab *notif_message)
{
const char *c_msk, *e_msk;
c_msk = env->ev_mask_msg;
e_msk = c_msk + strlen(c_msk);
for (; *c_msk != '\0'; c_msk++)
{
if (*c_msk != '@') {
if (ab_append_chr(notif_message, *c_msk) < 0)
break;
continue;
}
/* Abort if there is no next char to look ahead. */
else if (c_msk + 1 >= e_msk)
break;
/* Look next char, in order to escape it if needed.
* If next is also '@',escape it.
*/
if (c_msk[1] == '@') {
if (ab_append_chr(notif_message, *c_msk) < 0)
break;
else {
c_msk++;
continue; /* skip next char (since we already read it). */
}
}
/* If not a number, abort. */
else if (!(c_msk[1] >= '0' && c_msk[1] <= '9')) {
log_msg("Warning: expected number at input, but found (%c), "
"the resulting message will be incomplete!\n",
c_msk[1]);
break;
}
/* Its a number, proceed the replacement. */
else
{
c_msk++;
if (!handle_match_replacement(notif_message, &c_msk, e_msk,
pmatch, env, log_ev))
{
break;
}
}
}
/* If we could parse the entire mask. */
return (*c_msk == '\0');
}
/**
* @brief Handles a log event with a regex match.
*
* @param ev Pointer to the log event.
* @param idx_env Index of the environment event.
*
* @return Returns 1 if the event was handled, 0 otherwise.
*/
static int handle_regex(struct log_event *ev, int idx_env)
{
char time_str[32] = {0};
regmatch_t pmatch[MAX_MATCHES] = {0};
struct str_ab notif_message;
struct notifier *self;
int ret;
int notif_idx;
struct env_event *env_ev;
env_ev = &env_events[idx_env];
notif_idx = env_ev->ev_notifier_idx;
self = ¬ifiers[notif_idx];
if (regexec(&env_ev->regex, ev->msg, MAX_MATCHES, pmatch, 0) == REG_NOMATCH)
return 0;
log_msg("> Environment event detected!\n");
log_msg("> type : regex\n");
log_msg("> expr : %s\n", env_ev->ev_match_str);
log_msg("> amnt sub expr: %zu\n", env_ev->regex.re_nsub);
log_msg("> notifier : %s\n", notifiers_str[notif_idx]);
ab_init(¬if_message);
/* Check if there are any subexpressions, if not, just format
* the message.
*/
if (env_ev->regex.re_nsub) {
if (!create_masked_message(env_ev, pmatch, ev, ¬if_message)) {
log_msg("Unable to create masked message!\n");
return 0;
}
if (ab_append_fmt(¬if_message, ", at: %s",
get_formatted_time(ev->timestamp, time_str)))
{
return 0;
}
}
else {
ret = ab_append_fmt(¬if_message,
"%s, at: %s",
env_ev->ev_mask_msg,
get_formatted_time(ev->timestamp, time_str));
if (ret)
return 0;
}
if (self->send_notification(self, notif_message.buff) < 0) {
log_msg("unable to send the notification through %s\n",
notifiers_str[notif_idx]);
}
return 1;
}
/**
* @brief Handles a log event with a substring match.
*
* @param ev Pointer to the log event.
* @param idx_env Index of the environment event.
*
* @return Returns 1 if the event was handled, 0 otherwise.
*/
static int handle_substr(struct log_event *ev, int idx_env)
{
int ret;
int notif_idx;
char time_str[32] = {0};
struct notifier *self;
struct env_event *env_ev;
struct str_ab notif_message;
env_ev = &env_events[idx_env];
notif_idx = env_ev->ev_notifier_idx;
self = ¬ifiers[notif_idx];
if (!strstr(ev->msg, env_ev->ev_match_str))
return 0;
log_msg("> Environment event detected!\n");
log_msg("> type: substr, match: (%s), notifier: %s\n",
env_ev->ev_match_str, notifiers_str[notif_idx]);
ab_init(¬if_message);
/* Format the message. */
ret = ab_append_fmt(¬if_message,
"%s, at: %s",
env_ev->ev_mask_msg,
get_formatted_time(ev->timestamp, time_str)
);
if (ret)
return 0;
if (self->send_notification(self, notif_message.buff) < 0) {
log_msg("unable to send the notification through %s\n",
notifiers_str[notif_idx]);
}
return 1;
}
/**
* @brief Given an environment-variable event, checks if it
* belongs to one of the registered events and then, handle
* it.
*
* @param ev Event to be processed.
*
* @return Returns the amount of matches, 0 if none (not handled).
*/
int process_environment_event(struct log_event *ev)
{
int i;
int handled;
for (i = 0, handled = 0; i < num_env_events; i++) {
if (env_events[i].ev_match_type == EVNT_SUBSTR)
handled += handle_substr(ev, i);
else
handled += handle_regex(ev, i);
}
return handled;
}
/**
* @brief Initialize environment variables events.
*
* @return Returns 0 if there is no environment event,
* 1 if there is at least one _and_ is successfully
* configured.
*/
int init_environment_events(void)
{
struct notifier *self;
char *tmp;
tmp = getenv("ENV_EVENTS");
if (!tmp || (str2int(&num_env_events, tmp) < 0) || num_env_events <= 0) {
log_msg("Environment events not detected, disabling...\n");
return (0);
}
if (num_env_events >= MAX_ENV_EVENTS)
panic("Environment ENV_EVENTS exceeds the maximum supported (%d/%d)\n",
num_env_events, MAX_ENV_EVENTS);
log_msg("%d environment event(s) found, registering...\n", num_env_events);
for (int i = 0; i < num_env_events; i++) {
/* EVENTn_MATCH_TYPE. */
env_events[i].ev_match_type = get_event_idx(i, "MATCH_TYPE",
match_types, MATCH_TYPES_LEN);
/* EVENTn_NOTIFIER. */
env_events[i].ev_notifier_idx = get_event_idx(i, "NOTIFIER",
notifiers_str, NUM_NOTIFIERS);
/* EVENTn_MATCH_STR. */
env_events[i].ev_match_str = get_event_str(i, "MATCH_STR");
/* EVENTn_MASK_MSG. */
env_events[i].ev_mask_msg = get_event_str(i, "MASK_MSG");
}
log_msg("Environment events summary:\n");
for (int i = 0; i < num_env_events; i++)
{
log_msg("EVENT%d_MATCH_TYPE: %s\n", i,
match_types[env_events[i].ev_match_type]);
log_msg("EVENT%d_MATCH_STR: %s\n", i, env_events[i].ev_match_str);
log_msg("EVENT%d_NOTIFIER: %s\n", i,
notifiers_str[env_events[i].ev_notifier_idx]);
log_msg("EVENT%d_MASK_MSG: %s\n\n", i, env_events[i].ev_mask_msg);
/* Try to setup notifier if not yet. */
self = ¬ifiers[env_events[i].ev_notifier_idx];
self->setup(self);
/* If regex, compile it first. */
if (env_events[i].ev_match_type == EVNT_REGEX) {
if (regcomp(
&env_events[i].regex,
env_events[i].ev_match_str,
REG_EXTENDED))
{
panic("Unable to compile regex (%s) for EVENT%d!!!",
env_events[i].ev_match_str, i);
}
}
}
return 1;
}