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
30 static int trap_fd = -1;
31 static int trap_lastsize;
32 static ino_t trap_ino = 0;
33 static char *buffer = NULL;
34 static int offset = 0;
35 static int force = 0;
36 static int overflow_warning = 0;
37
38 extern unsigned char process_type, program_type;
39 extern int server_num, process_num;
40
DBget_lastsize()41 static void DBget_lastsize()
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 trap_lastsize = atoi(row[0]);
57
58 DBfree_result(result);
59
60 DBcommit();
61 }
62
DBupdate_lastsize()63 static void DBupdate_lastsize()
64 {
65 DBbegin();
66 DBexecute("update globalvars set snmp_lastsize=%d", 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;
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_malloc(itemids, sizeof(zbx_uint64_t) * num);
100 states = zbx_malloc(states, sizeof(unsigned char) * num);
101 lastclocks = zbx_malloc(lastclocks, sizeof(int) * num);
102 errcodes = zbx_malloc(errcodes, sizeof(int) * num);
103 results = 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,
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 (SUCCEED != regexp_match_ex(®exps, trap, regex, ZBX_CASE_SENSITIVE))
152 goto next;
153 }
154
155 if (SUCCEED == set_result_type(&results[i], items[i].value_type, items[i].data_type, trap))
156 errcodes[i] = SUCCEED;
157 else
158 errcodes[i] = NOTSUPPORTED;
159 ret = SUCCEED;
160 next:
161 free_request(&request);
162 }
163
164 if (FAIL == ret && -1 != fb)
165 {
166 if (SUCCEED == set_result_type(&results[fb], items[fb].value_type, items[fb].data_type, trap))
167 errcodes[fb] = SUCCEED;
168 else
169 errcodes[fb] = NOTSUPPORTED;
170 ret = SUCCEED;
171 }
172
173 for (i = 0; i < num; i++)
174 {
175 switch (errcodes[i])
176 {
177 case SUCCEED:
178 if (ITEM_VALUE_TYPE_LOG == items[i].value_type)
179 {
180 calc_timestamp(results[i].log->value, &results[i].log->timestamp,
181 items[i].logtimefmt);
182 }
183
184 items[i].state = ITEM_STATE_NORMAL;
185 dc_add_history(items[i].itemid, items[i].value_type, items[i].flags, &results[i],
186 ts, items[i].state, NULL);
187
188 itemids[i] = items[i].itemid;
189 states[i] = items[i].state;
190 lastclocks[i] = ts->sec;
191 break;
192 case NOTSUPPORTED:
193 items[i].state = ITEM_STATE_NOTSUPPORTED;
194 dc_add_history(items[i].itemid, items[i].value_type, items[i].flags, NULL,
195 ts, items[i].state, results[i].msg);
196
197 itemids[i] = items[i].itemid;
198 states[i] = items[i].state;
199 lastclocks[i] = ts->sec;
200 break;
201 }
202
203 zbx_free(items[i].key);
204 free_result(&results[i]);
205 }
206
207 zbx_free(results);
208
209 DCrequeue_items(itemids, states, lastclocks, NULL, NULL, errcodes, num);
210
211 zbx_free(errcodes);
212 zbx_free(lastclocks);
213 zbx_free(states);
214 zbx_free(itemids);
215
216 DCconfig_clean_items(items, NULL, num);
217 zbx_free(items);
218
219 zbx_regexp_clean_expressions(®exps);
220 zbx_vector_ptr_destroy(®exps);
221
222 dc_flush_history();
223
224 return ret;
225 }
226
227 /******************************************************************************
228 * *
229 * Function: process_trap *
230 * *
231 * Purpose: process a single trap *
232 * *
233 * Parameters: addr - [IN] address of the target interface(s) *
234 * begin - [IN] beginning of the trap message *
235 * end - [IN] end of the trap message *
236 * *
237 * Author: Rudolfs Kreicbergs *
238 * *
239 ******************************************************************************/
process_trap(const char * addr,char * begin,char * end)240 static void process_trap(const char *addr, char *begin, char *end)
241 {
242 zbx_timespec_t ts;
243 zbx_uint64_t *interfaceids = NULL;
244 int count, i, ret = FAIL;
245 char *trap = NULL;
246
247 zbx_timespec(&ts);
248 trap = zbx_dsprintf(trap, "%s%s", begin, end);
249
250 count = DCconfig_get_snmp_interfaceids_by_addr(addr, &interfaceids);
251
252 for (i = 0; i < count; i++)
253 {
254 if (SUCCEED == process_trap_for_interface(interfaceids[i], trap, &ts))
255 ret = SUCCEED;
256 }
257
258 if (FAIL == ret)
259 {
260 zbx_config_t cfg;
261
262 zbx_config_get(&cfg, ZBX_CONFIG_FLAGS_SNMPTRAP_LOGGING);
263
264 if (ZBX_SNMPTRAP_LOGGING_ENABLED == cfg.snmptrap_logging)
265 zabbix_log(LOG_LEVEL_WARNING, "unmatched trap received from \"%s\": %s", addr, trap);
266
267 zbx_config_clean(&cfg);
268 }
269
270 zbx_free(interfaceids);
271 zbx_free(trap);
272 }
273
274 /******************************************************************************
275 * *
276 * Function: parse_traps *
277 * *
278 * Purpose: split traps and process them with process_trap() *
279 * *
280 * Author: Rudolfs Kreicbergs *
281 * *
282 ******************************************************************************/
parse_traps(int flag)283 static void parse_traps(int flag)
284 {
285 char *c, *line, *begin = NULL, *end = NULL, *addr = NULL, *pzbegin, *pzaddr = NULL, *pzdate = NULL;
286
287 c = line = buffer;
288
289 while ('\0' != *c)
290 {
291 if ('\n' == *c)
292 {
293 line = ++c;
294 continue;
295 }
296
297 if (0 != strncmp(c, "ZBXTRAP", 7))
298 {
299 c++;
300 continue;
301 }
302
303 pzbegin = c;
304
305 c += 7; /* c now points to the delimiter between "ZBXTRAP" and address */
306
307 while ('\0' != *c && NULL != strchr(ZBX_WHITESPACE, *c))
308 c++;
309
310 /* c now points to the address */
311
312 /* process the previous trap */
313 if (NULL != begin)
314 {
315 *(line - 1) = '\0';
316 *pzdate = '\0';
317 *pzaddr = '\0';
318
319 process_trap(addr, begin, end);
320 end = NULL;
321 }
322
323 /* parse the current trap */
324 begin = line;
325 addr = c;
326 pzdate = pzbegin;
327
328 while ('\0' != *c && NULL == strchr(ZBX_WHITESPACE, *c))
329 c++;
330
331 pzaddr = c;
332
333 end = c + 1; /* the rest of the trap */
334 }
335
336 if (0 == flag)
337 {
338 if (NULL == begin)
339 offset = c - buffer;
340 else
341 offset = c - begin;
342
343 if (offset == MAX_BUFFER_LEN - 1)
344 {
345 if (NULL != end)
346 {
347 zabbix_log(LOG_LEVEL_WARNING, "SNMP trapper buffer is full,"
348 " trap data might be truncated");
349 parse_traps(1);
350 }
351 else
352 zabbix_log(LOG_LEVEL_WARNING, "failed to find trap in SNMP trapper file");
353
354 offset = 0;
355 *buffer = '\0';
356 }
357 else
358 {
359 if (NULL != begin && begin != buffer)
360 memmove(buffer, begin, offset + 1);
361 }
362 }
363 else
364 {
365 if (NULL != end)
366 {
367 *(line - 1) = '\0';
368 *pzdate = '\0';
369 *pzaddr = '\0';
370
371 process_trap(addr, begin, end);
372 offset = 0;
373 *buffer = '\0';
374 }
375 else
376 {
377 zabbix_log(LOG_LEVEL_WARNING, "invalid trap data found \"%s\"", buffer);
378 offset = 0;
379 *buffer = '\0';
380 }
381 }
382 }
383
384 /******************************************************************************
385 * *
386 * Function: read_traps *
387 * *
388 * Purpose: read the traps and then parse them with parse_traps() *
389 * *
390 * Author: Rudolfs Kreicbergs *
391 * *
392 ******************************************************************************/
read_traps()393 static int read_traps()
394 {
395 const char *__function_name = "read_traps";
396 int nbytes = 0;
397
398 zabbix_log(LOG_LEVEL_DEBUG, "In %s() lastsize:%d", __function_name, trap_lastsize);
399
400 if ((off_t)-1 == lseek(trap_fd, (off_t)trap_lastsize, SEEK_SET))
401 {
402 zabbix_log(LOG_LEVEL_WARNING, "cannot set position to %d for \"%s\": %s", trap_lastsize,
403 CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
404 goto exit;
405 }
406
407 if (-1 == (nbytes = read(trap_fd, buffer + offset, MAX_BUFFER_LEN - offset - 1)))
408 {
409 zabbix_log(LOG_LEVEL_WARNING, "cannot read from SNMP trapper file \"%s\": %s", CONFIG_SNMPTRAP_FILE,
410 zbx_strerror(errno));
411 goto exit;
412 }
413
414 if (0 < nbytes)
415 {
416 if (ZBX_SNMP_TRAPFILE_MAX_SIZE <= (zbx_uint64_t)trap_lastsize + nbytes)
417 {
418 nbytes = 0;
419 goto exit;
420 }
421
422 buffer[nbytes + offset] = '\0';
423 trap_lastsize += nbytes;
424 DBupdate_lastsize();
425 parse_traps(0);
426 }
427 exit:
428 zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
429 return nbytes;
430 }
431
432 /******************************************************************************
433 * *
434 * Function: close_trap_file *
435 * *
436 * Purpose: close trap file and reset lastsize *
437 * *
438 * Author: Rudolfs Kreicbergs *
439 * *
440 * Comments: !!! do not reset lastsize elsewhere !!! *
441 * *
442 ******************************************************************************/
close_trap_file()443 static void close_trap_file()
444 {
445 if (-1 != trap_fd)
446 close(trap_fd);
447
448 trap_fd = -1;
449 trap_lastsize = 0;
450 DBupdate_lastsize();
451 }
452
453 /******************************************************************************
454 * *
455 * Function: open_trap_file *
456 * *
457 * Purpose: open the trap file and get it's node number *
458 * *
459 * Return value: file descriptor of the opened file or -1 otherwise *
460 * *
461 * Author: Rudolfs Kreicbergs *
462 * *
463 ******************************************************************************/
open_trap_file()464 static int open_trap_file()
465 {
466 zbx_stat_t file_buf;
467
468 if (0 != zbx_stat(CONFIG_SNMPTRAP_FILE, &file_buf))
469 {
470 zabbix_log(LOG_LEVEL_CRIT, "cannot stat SNMP trapper file \"%s\": %s", CONFIG_SNMPTRAP_FILE,
471 zbx_strerror(errno));
472 goto out;
473 }
474
475 if (ZBX_SNMP_TRAPFILE_MAX_SIZE <= (zbx_uint64_t)file_buf.st_size)
476 {
477 if (0 == overflow_warning)
478 {
479 zabbix_log(LOG_LEVEL_CRIT, "cannot process SNMP trapper file \"%s\":"
480 " file size exceeds the maximum supported size of 2 GB",
481 CONFIG_SNMPTRAP_FILE);
482 overflow_warning = 1;
483 }
484 goto out;
485 }
486
487 overflow_warning = 0;
488
489 if (-1 == (trap_fd = open(CONFIG_SNMPTRAP_FILE, O_RDONLY)))
490 {
491 if (ENOENT != errno) /* file exists but cannot be opened */
492 {
493 zabbix_log(LOG_LEVEL_CRIT, "cannot open SNMP trapper file \"%s\": %s", CONFIG_SNMPTRAP_FILE,
494 zbx_strerror(errno));
495 }
496 goto out;
497 }
498
499 trap_ino = file_buf.st_ino; /* a new file was opened */
500 out:
501 return trap_fd;
502 }
503
504 /******************************************************************************
505 * *
506 * Function: get_latest_data *
507 * *
508 * Purpose: Open the latest trap file. If the current file has been rotated, *
509 * process that and then open the latest file. *
510 * *
511 * Return value: SUCCEED - there are new traps to be parsed *
512 * FAIL - there are no new traps or trap file does not exist *
513 * *
514 * Author: Rudolfs Kreicbergs *
515 * *
516 ******************************************************************************/
get_latest_data()517 static int get_latest_data()
518 {
519 zbx_stat_t file_buf;
520
521 if (-1 != trap_fd) /* a trap file is already open */
522 {
523 if (0 != zbx_stat(CONFIG_SNMPTRAP_FILE, &file_buf))
524 {
525 /* file might have been renamed or deleted, process the current file */
526
527 if (ENOENT != errno)
528 {
529 zabbix_log(LOG_LEVEL_CRIT, "cannot stat SNMP trapper file \"%s\": %s",
530 CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
531 }
532
533 while (0 < read_traps())
534 ;
535
536 if (0 != offset)
537 parse_traps(1);
538
539 close_trap_file();
540 }
541 else if (ZBX_SNMP_TRAPFILE_MAX_SIZE <= (zbx_uint64_t)file_buf.st_size)
542 {
543 close_trap_file();
544 }
545 else if (file_buf.st_ino != trap_ino || file_buf.st_size < trap_lastsize)
546 {
547 /* file has been rotated, process the current file */
548
549 while (0 < read_traps())
550 ;
551
552 if (0 != offset)
553 parse_traps(1);
554
555 close_trap_file();
556 }
557 else if (file_buf.st_size == trap_lastsize)
558 {
559 if (1 == force)
560 {
561 parse_traps(1);
562 force = 0;
563 }
564 else if (0 != offset && 0 == force)
565 {
566 force = 1;
567 }
568
569 return FAIL; /* no new traps */
570 }
571 }
572
573 force = 0;
574
575 if (-1 == trap_fd && -1 == open_trap_file())
576 return FAIL;
577
578 return SUCCEED;
579 }
580
581 /******************************************************************************
582 * *
583 * Function: snmptrapper_thread *
584 * *
585 * Purpose: SNMP trap reader's entry point *
586 * *
587 * Author: Rudolfs Kreicbergs *
588 * *
589 ******************************************************************************/
ZBX_THREAD_ENTRY(snmptrapper_thread,args)590 ZBX_THREAD_ENTRY(snmptrapper_thread, args)
591 {
592 const char *__function_name = "snmptrapper_thread";
593 double sec;
594
595 process_type = ((zbx_thread_args_t *)args)->process_type;
596 server_num = ((zbx_thread_args_t *)args)->server_num;
597 process_num = ((zbx_thread_args_t *)args)->process_num;
598
599 zabbix_log(LOG_LEVEL_INFORMATION, "%s #%d started [%s #%d]", get_program_type_string(program_type),
600 server_num, get_process_type_string(process_type), process_num);
601
602 zabbix_log(LOG_LEVEL_DEBUG, "In %s() trapfile:'%s'", __function_name, CONFIG_SNMPTRAP_FILE);
603
604 zbx_setproctitle("%s [connecting to the database]", get_process_type_string(process_type));
605
606 DBconnect(ZBX_DB_CONNECT_NORMAL);
607
608 DBget_lastsize();
609
610 buffer = zbx_malloc(buffer, MAX_BUFFER_LEN);
611 *buffer = '\0';
612
613 for (;;)
614 {
615 zbx_handle_log();
616
617 zbx_setproctitle("%s [processing data]", get_process_type_string(process_type));
618
619 sec = zbx_time();
620 while (SUCCEED == get_latest_data())
621 read_traps();
622 sec = zbx_time() - sec;
623
624 zbx_setproctitle("%s [processed data in " ZBX_FS_DBL " sec, idle 1 sec]",
625 get_process_type_string(process_type), sec);
626
627 zbx_sleep_loop(1);
628
629 #if !defined(_WINDOWS) && defined(HAVE_RESOLV_H)
630 zbx_update_resolver_conf(); /* handle /etc/resolv.conf update */
631 #endif
632 }
633
634 zbx_free(buffer);
635
636 if (-1 != trap_fd)
637 close(trap_fd);
638 }
639