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