1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **/
19
20 #include "common.h"
21 #include "dbcache.h"
22 #include "zbxself.h"
23 #include "daemon.h"
24 #include "log.h"
25 #include "proxy.h"
26 #include "snmptrapper.h"
27 #include "zbxserver.h"
28 #include "zbxregexp.h"
29 #include "preproc.h"
30
31 static int trap_fd = -1;
32 static off_t trap_lastsize;
33 static ino_t trap_ino = 0;
34 static char *buffer = NULL;
35 static int offset = 0;
36 static int force = 0;
37
38 extern unsigned char process_type, program_type;
39 extern int server_num, process_num;
40
DBget_lastsize(void)41 static void DBget_lastsize(void)
42 {
43 DB_RESULT result;
44 DB_ROW row;
45
46 DBbegin();
47
48 result = DBselect("select snmp_lastsize from globalvars");
49
50 if (NULL == (row = DBfetch(result)))
51 {
52 DBexecute("insert into globalvars (globalvarid,snmp_lastsize) values (1,0)");
53 trap_lastsize = 0;
54 }
55 else
56 ZBX_STR2UINT64(trap_lastsize, row[0]);
57
58 DBfree_result(result);
59
60 DBcommit();
61 }
62
DBupdate_lastsize(void)63 static void DBupdate_lastsize(void)
64 {
65 DBbegin();
66 DBexecute("update globalvars set snmp_lastsize=%lld", (long long int)trap_lastsize);
67 DBcommit();
68 }
69
70 /******************************************************************************
71 * *
72 * Function: process_trap_for_interface *
73 * *
74 * Purpose: add trap to all matching items for the specified interface *
75 * *
76 * Return value: SUCCEED - a matching item was found *
77 * FAIL - no matching item was found (including fallback items) *
78 * *
79 * Author: Rudolfs Kreicbergs *
80 * *
81 ******************************************************************************/
process_trap_for_interface(zbx_uint64_t interfaceid,char * trap,zbx_timespec_t * ts)82 static int process_trap_for_interface(zbx_uint64_t interfaceid, char *trap, zbx_timespec_t *ts)
83 {
84 DC_ITEM *items = NULL;
85 const char *regex;
86 char error[ITEM_ERROR_LEN_MAX];
87 size_t num, i;
88 int ret = FAIL, fb = -1, *lastclocks = NULL, *errcodes = NULL, value_type, regexp_ret;
89 zbx_uint64_t *itemids = NULL;
90 AGENT_RESULT *results = NULL;
91 AGENT_REQUEST request;
92 zbx_vector_ptr_t regexps;
93
94 zbx_vector_ptr_create(®exps);
95
96 num = DCconfig_get_snmp_items_by_interfaceid(interfaceid, &items);
97
98 itemids = (zbx_uint64_t *)zbx_malloc(itemids, sizeof(zbx_uint64_t) * num);
99 lastclocks = (int *)zbx_malloc(lastclocks, sizeof(int) * num);
100 errcodes = (int *)zbx_malloc(errcodes, sizeof(int) * num);
101 results = (AGENT_RESULT *)zbx_malloc(results, sizeof(AGENT_RESULT) * num);
102
103 for (i = 0; i < num; i++)
104 {
105 init_result(&results[i]);
106 errcodes[i] = FAIL;
107
108 items[i].key = zbx_strdup(items[i].key, items[i].key_orig);
109 if (SUCCEED != substitute_key_macros(&items[i].key, NULL, &items[i], NULL, NULL,
110 MACRO_TYPE_ITEM_KEY, error, sizeof(error)))
111 {
112 SET_MSG_RESULT(&results[i], zbx_strdup(NULL, error));
113 errcodes[i] = NOTSUPPORTED;
114 continue;
115 }
116
117 if (0 == strcmp(items[i].key, "snmptrap.fallback"))
118 {
119 fb = i;
120 continue;
121 }
122
123 init_request(&request);
124
125 if (SUCCEED != parse_item_key(items[i].key, &request))
126 goto next;
127
128 if (0 != strcmp(get_rkey(&request), "snmptrap"))
129 goto next;
130
131 if (1 < get_rparams_num(&request))
132 goto next;
133
134 if (NULL != (regex = get_rparam(&request, 0)))
135 {
136 if ('@' == *regex)
137 {
138 DCget_expressions_by_name(®exps, regex + 1);
139
140 if (0 == regexps.values_num)
141 {
142 SET_MSG_RESULT(&results[i], zbx_dsprintf(NULL,
143 "Global regular expression \"%s\" does not exist.", regex + 1));
144 errcodes[i] = NOTSUPPORTED;
145 goto next;
146 }
147 }
148
149 if (ZBX_REGEXP_NO_MATCH == (regexp_ret = regexp_match_ex(®exps, trap, regex,
150 ZBX_CASE_SENSITIVE)))
151 {
152 goto next;
153 }
154 else if (FAIL == regexp_ret)
155 {
156 SET_MSG_RESULT(&results[i], zbx_dsprintf(NULL,
157 "Invalid regular expression \"%s\".", regex));
158 errcodes[i] = NOTSUPPORTED;
159 goto next;
160 }
161 }
162
163 value_type = (ITEM_VALUE_TYPE_LOG == items[i].value_type ? ITEM_VALUE_TYPE_LOG : ITEM_VALUE_TYPE_TEXT);
164 set_result_type(&results[i], value_type, trap);
165 errcodes[i] = SUCCEED;
166 ret = SUCCEED;
167 next:
168 free_request(&request);
169 }
170
171 if (FAIL == ret && -1 != fb)
172 {
173 value_type = (ITEM_VALUE_TYPE_LOG == items[fb].value_type ? ITEM_VALUE_TYPE_LOG : ITEM_VALUE_TYPE_TEXT);
174 set_result_type(&results[fb], value_type, trap);
175 errcodes[fb] = SUCCEED;
176 ret = SUCCEED;
177 }
178
179 for (i = 0; i < num; i++)
180 {
181 switch (errcodes[i])
182 {
183 case SUCCEED:
184 if (ITEM_VALUE_TYPE_LOG == items[i].value_type)
185 {
186 calc_timestamp(results[i].log->value, &results[i].log->timestamp,
187 items[i].logtimefmt);
188 }
189
190 items[i].state = ITEM_STATE_NORMAL;
191 zbx_preprocess_item_value(items[i].itemid, items[i].host.hostid, items[i].value_type, items[i].flags,
192 &results[i], ts, items[i].state, NULL);
193
194 itemids[i] = items[i].itemid;
195 lastclocks[i] = ts->sec;
196 break;
197 case NOTSUPPORTED:
198 items[i].state = ITEM_STATE_NOTSUPPORTED;
199 zbx_preprocess_item_value(items[i].itemid, items[i].host.hostid, items[i].value_type, items[i].flags, NULL,
200 ts, items[i].state, results[i].msg);
201
202 itemids[i] = items[i].itemid;
203 lastclocks[i] = ts->sec;
204 break;
205 }
206
207 zbx_free(items[i].key);
208 free_result(&results[i]);
209 }
210
211 zbx_free(results);
212
213 DCrequeue_items(itemids, lastclocks, errcodes, num);
214
215 zbx_free(errcodes);
216 zbx_free(lastclocks);
217 zbx_free(itemids);
218
219 DCconfig_clean_items(items, NULL, num);
220 zbx_free(items);
221
222 zbx_regexp_clean_expressions(®exps);
223 zbx_vector_ptr_destroy(®exps);
224
225 zbx_preprocessor_flush();
226
227 return ret;
228 }
229
230 /******************************************************************************
231 * *
232 * Function: process_trap *
233 * *
234 * Purpose: process a single trap *
235 * *
236 * Parameters: addr - [IN] address of the target interface(s) *
237 * begin - [IN] beginning of the trap message *
238 * end - [IN] end of the trap message *
239 * *
240 * Author: Rudolfs Kreicbergs *
241 * *
242 ******************************************************************************/
process_trap(const char * addr,char * begin,char * end)243 static void process_trap(const char *addr, char *begin, char *end)
244 {
245 zbx_timespec_t ts;
246 zbx_uint64_t *interfaceids = NULL;
247 int count, i, ret = FAIL;
248 char *trap = NULL;
249
250 zbx_timespec(&ts);
251 trap = zbx_dsprintf(trap, "%s%s", begin, end);
252
253 count = DCconfig_get_snmp_interfaceids_by_addr(addr, &interfaceids);
254
255 for (i = 0; i < count; i++)
256 {
257 if (SUCCEED == process_trap_for_interface(interfaceids[i], trap, &ts))
258 ret = SUCCEED;
259 }
260
261 if (FAIL == ret)
262 {
263 zbx_config_t cfg;
264
265 zbx_config_get(&cfg, ZBX_CONFIG_FLAGS_SNMPTRAP_LOGGING);
266
267 if (ZBX_SNMPTRAP_LOGGING_ENABLED == cfg.snmptrap_logging)
268 zabbix_log(LOG_LEVEL_WARNING, "unmatched trap received from \"%s\": %s", addr, trap);
269
270 zbx_config_clean(&cfg);
271 }
272
273 zbx_free(interfaceids);
274 zbx_free(trap);
275 }
276
277 /******************************************************************************
278 * *
279 * Function: parse_traps *
280 * *
281 * Purpose: split traps and process them with process_trap() *
282 * *
283 * Author: Rudolfs Kreicbergs *
284 * *
285 ******************************************************************************/
parse_traps(int flag)286 static void parse_traps(int flag)
287 {
288 char *c, *line, *begin = NULL, *end = NULL, *addr = NULL, *pzbegin, *pzaddr = NULL, *pzdate = NULL;
289
290 c = line = buffer;
291
292 while ('\0' != *c)
293 {
294 if ('\n' == *c)
295 {
296 line = ++c;
297 continue;
298 }
299
300 if (0 != strncmp(c, "ZBXTRAP", 7))
301 {
302 c++;
303 continue;
304 }
305
306 pzbegin = c;
307
308 c += 7; /* c now points to the delimiter between "ZBXTRAP" and address */
309
310 while ('\0' != *c && NULL != strchr(ZBX_WHITESPACE, *c))
311 c++;
312
313 /* c now points to the address */
314
315 /* process the previous trap */
316 if (NULL != begin)
317 {
318 *(line - 1) = '\0';
319 *pzdate = '\0';
320 *pzaddr = '\0';
321
322 process_trap(addr, begin, end);
323 end = NULL;
324 }
325
326 /* parse the current trap */
327 begin = line;
328 addr = c;
329 pzdate = pzbegin;
330
331 while ('\0' != *c && NULL == strchr(ZBX_WHITESPACE, *c))
332 c++;
333
334 pzaddr = c;
335
336 end = c + 1; /* the rest of the trap */
337 }
338
339 if (0 == flag)
340 {
341 if (NULL == begin)
342 offset = c - buffer;
343 else
344 offset = c - begin;
345
346 if (offset == MAX_BUFFER_LEN - 1)
347 {
348 if (NULL != end)
349 {
350 zabbix_log(LOG_LEVEL_WARNING, "SNMP trapper buffer is full,"
351 " trap data might be truncated");
352 parse_traps(1);
353 }
354 else
355 zabbix_log(LOG_LEVEL_WARNING, "failed to find trap in SNMP trapper file");
356
357 offset = 0;
358 *buffer = '\0';
359 }
360 else
361 {
362 if (NULL != begin && begin != buffer)
363 memmove(buffer, begin, offset + 1);
364 }
365 }
366 else
367 {
368 if (NULL != end)
369 {
370 *(line - 1) = '\0';
371 *pzdate = '\0';
372 *pzaddr = '\0';
373
374 process_trap(addr, begin, end);
375 offset = 0;
376 *buffer = '\0';
377 }
378 else
379 {
380 zabbix_log(LOG_LEVEL_WARNING, "invalid trap data found \"%s\"", buffer);
381 offset = 0;
382 *buffer = '\0';
383 }
384 }
385 }
386
387 /******************************************************************************
388 * *
389 * Function: delay_trap_logs *
390 * *
391 * Purpose: delay SNMP trapper file related issue log entries for 60 seconds *
392 * unless this is the first time this issue has occurred *
393 * *
394 * Parameters: error - [IN] string containing log entry text *
395 * log_level - [IN] the log entry log level *
396 * *
397 ******************************************************************************/
delay_trap_logs(char * error,int log_level)398 static void delay_trap_logs(char *error, int log_level)
399 {
400 int now;
401 static int lastlogtime = 0;
402 static zbx_hash_t last_error_hash = 0;
403 zbx_hash_t error_hash;
404
405 now = (int)time(NULL);
406 error_hash = zbx_default_string_hash_func(error);
407
408 if (LOG_ENTRY_INTERVAL_DELAY <= now - lastlogtime || last_error_hash != error_hash)
409 {
410 zabbix_log(log_level, "%s", error);
411 lastlogtime = now;
412 last_error_hash = error_hash;
413 }
414 }
415
416 /******************************************************************************
417 * *
418 * Function: read_traps *
419 * *
420 * Purpose: read the traps and then parse them with parse_traps() *
421 * *
422 * Author: Rudolfs Kreicbergs *
423 * *
424 ******************************************************************************/
read_traps(void)425 static int read_traps(void)
426 {
427 int nbytes = 0;
428 char *error = NULL;
429
430 zabbix_log(LOG_LEVEL_DEBUG, "In %s() lastsize: %lld", __func__, (long long int)trap_lastsize);
431
432 if (-1 == lseek(trap_fd, trap_lastsize, SEEK_SET))
433 {
434 error = zbx_dsprintf(error, "cannot set position to %lld for \"%s\": %s", (long long int)trap_lastsize,
435 CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
436 delay_trap_logs(error, LOG_LEVEL_WARNING);
437 goto out;
438 }
439
440 if (-1 == (nbytes = read(trap_fd, buffer + offset, MAX_BUFFER_LEN - offset - 1)))
441 {
442 error = zbx_dsprintf(error, "cannot read from SNMP trapper file \"%s\": %s",
443 CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
444 delay_trap_logs(error, LOG_LEVEL_WARNING);
445 goto out;
446 }
447
448 if (0 < nbytes)
449 {
450 buffer[nbytes + offset] = '\0';
451 trap_lastsize += nbytes;
452 DBupdate_lastsize();
453 parse_traps(0);
454 }
455 out:
456 zbx_free(error);
457
458 zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
459
460 return nbytes;
461 }
462
463 /******************************************************************************
464 * *
465 * Function: close_trap_file *
466 * *
467 * Purpose: close trap file and reset lastsize *
468 * *
469 * Author: Rudolfs Kreicbergs *
470 * *
471 * Comments: !!! do not reset lastsize elsewhere !!! *
472 * *
473 ******************************************************************************/
close_trap_file(void)474 static void close_trap_file(void)
475 {
476 if (-1 != trap_fd)
477 close(trap_fd);
478
479 trap_fd = -1;
480 trap_lastsize = 0;
481 DBupdate_lastsize();
482 }
483
484 /******************************************************************************
485 * *
486 * Function: open_trap_file *
487 * *
488 * Purpose: open the trap file and get it's node number *
489 * *
490 * Return value: file descriptor of the opened file or -1 otherwise *
491 * *
492 * Author: Rudolfs Kreicbergs *
493 * *
494 ******************************************************************************/
open_trap_file(void)495 static int open_trap_file(void)
496 {
497 zbx_stat_t file_buf;
498 char *error = NULL;
499
500 if (-1 == (trap_fd = open(CONFIG_SNMPTRAP_FILE, O_RDONLY)))
501 {
502 if (ENOENT != errno) /* file exists but cannot be opened */
503 {
504 error = zbx_dsprintf(error, "cannot open SNMP trapper file \"%s\": %s",
505 CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
506 delay_trap_logs(error, LOG_LEVEL_CRIT);
507 }
508 goto out;
509 }
510
511 if (0 != zbx_fstat(trap_fd, &file_buf))
512 {
513 error = zbx_dsprintf(error, "cannot stat SNMP trapper file \"%s\": %s", CONFIG_SNMPTRAP_FILE,
514 zbx_strerror(errno));
515 delay_trap_logs(error, LOG_LEVEL_CRIT);
516 close(trap_fd);
517 trap_fd = -1;
518 goto out;
519 }
520
521 trap_ino = file_buf.st_ino; /* a new file was opened */
522 out:
523 zbx_free(error);
524
525 return trap_fd;
526 }
527
528 /******************************************************************************
529 * *
530 * Function: get_latest_data *
531 * *
532 * Purpose: Open the latest trap file. If the current file has been rotated, *
533 * process that and then open the latest file. *
534 * *
535 * Return value: SUCCEED - there are new traps to be parsed *
536 * FAIL - there are no new traps or trap file does not exist *
537 * *
538 * Author: Rudolfs Kreicbergs *
539 * *
540 ******************************************************************************/
get_latest_data(void)541 static int get_latest_data(void)
542 {
543 zbx_stat_t file_buf;
544
545 if (-1 != trap_fd) /* a trap file is already open */
546 {
547 if (0 != zbx_stat(CONFIG_SNMPTRAP_FILE, &file_buf))
548 {
549 /* file might have been renamed or deleted, process the current file */
550
551 if (ENOENT != errno)
552 {
553 zabbix_log(LOG_LEVEL_CRIT, "cannot stat SNMP trapper file \"%s\": %s",
554 CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
555 }
556
557 while (0 < read_traps())
558 ;
559
560 if (0 != offset)
561 parse_traps(1);
562
563 close_trap_file();
564 }
565 else if (file_buf.st_ino != trap_ino || file_buf.st_size < trap_lastsize)
566 {
567 /* file has been rotated, process the current file */
568
569 while (0 < read_traps())
570 ;
571
572 if (0 != offset)
573 parse_traps(1);
574
575 close_trap_file();
576 }
577 /* in case when file access permission is changed and read permission is denied */
578 else if (0 != access(CONFIG_SNMPTRAP_FILE, R_OK))
579 {
580 if (EACCES == errno)
581 close_trap_file();
582 }
583 else if (file_buf.st_size == trap_lastsize)
584 {
585 if (1 == force)
586 {
587 parse_traps(1);
588 force = 0;
589 }
590 else if (0 != offset && 0 == force)
591 {
592 force = 1;
593 }
594
595 return FAIL; /* no new traps */
596 }
597 }
598
599 force = 0;
600
601 if (-1 == trap_fd && -1 == open_trap_file())
602 return FAIL;
603
604 return SUCCEED;
605 }
606
607 /******************************************************************************
608 * *
609 * Function: main_snmptrapper_loop *
610 * *
611 * Purpose: SNMP trap reader's entry point *
612 * *
613 * Author: Rudolfs Kreicbergs *
614 * *
615 ******************************************************************************/
ZBX_THREAD_ENTRY(snmptrapper_thread,args)616 ZBX_THREAD_ENTRY(snmptrapper_thread, args)
617 {
618 double sec;
619
620 process_type = ((zbx_thread_args_t *)args)->process_type;
621 server_num = ((zbx_thread_args_t *)args)->server_num;
622 process_num = ((zbx_thread_args_t *)args)->process_num;
623
624 zabbix_log(LOG_LEVEL_INFORMATION, "%s #%d started [%s #%d]", get_program_type_string(program_type),
625 server_num, get_process_type_string(process_type), process_num);
626
627 zabbix_log(LOG_LEVEL_DEBUG, "In %s() trapfile:'%s'", __func__, CONFIG_SNMPTRAP_FILE);
628
629 update_selfmon_counter(ZBX_PROCESS_STATE_BUSY);
630
631 zbx_setproctitle("%s [connecting to the database]", get_process_type_string(process_type));
632
633 DBconnect(ZBX_DB_CONNECT_NORMAL);
634
635 DBget_lastsize();
636
637 buffer = (char *)zbx_malloc(buffer, MAX_BUFFER_LEN);
638 *buffer = '\0';
639
640 while (ZBX_IS_RUNNING())
641 {
642 sec = zbx_time();
643 zbx_update_env(sec);
644
645 zbx_setproctitle("%s [processing data]", get_process_type_string(process_type));
646
647 while (ZBX_IS_RUNNING() && SUCCEED == get_latest_data())
648 read_traps();
649 sec = zbx_time() - sec;
650
651 zbx_setproctitle("%s [processed data in " ZBX_FS_DBL " sec, idle 1 sec]",
652 get_process_type_string(process_type), sec);
653
654 zbx_sleep_loop(1);
655 }
656
657 zbx_free(buffer);
658
659 if (-1 != trap_fd)
660 close(trap_fd);
661
662 zbx_setproctitle("%s #%d [terminated]", get_process_type_string(process_type), process_num);
663
664 while (1)
665 zbx_sleep(SEC_PER_MIN);
666 }
667