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(&regexps);
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,
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(&regexps, 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(&regexps, 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].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].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(&regexps);
228 	zbx_vector_ptr_destroy(&regexps);
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 	const char	*__function_name = "read_traps";
433 	int		nbytes = 0;
434 	char		*error = NULL;
435 
436 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() lastsize: %lld", __function_name, (long long int)trap_lastsize);
437 
438 	if (-1 == lseek(trap_fd, trap_lastsize, SEEK_SET))
439 	{
440 		error = zbx_dsprintf(error, "cannot set position to %lld for \"%s\": %s", (long long int)trap_lastsize,
441 				CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
442 		delay_trap_logs(error, LOG_LEVEL_WARNING);
443 		goto out;
444 	}
445 
446 	if (-1 == (nbytes = read(trap_fd, buffer + offset, MAX_BUFFER_LEN - offset - 1)))
447 	{
448 		error = zbx_dsprintf(error, "cannot read from SNMP trapper file \"%s\": %s",
449 				CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
450 		delay_trap_logs(error, LOG_LEVEL_WARNING);
451 		goto out;
452 	}
453 
454 	if (0 < nbytes)
455 	{
456 		buffer[nbytes + offset] = '\0';
457 		trap_lastsize += nbytes;
458 		DBupdate_lastsize();
459 		parse_traps(0);
460 	}
461 out:
462 	zbx_free(error);
463 
464 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
465 
466 	return nbytes;
467 }
468 
469 /******************************************************************************
470  *                                                                            *
471  * Function: close_trap_file                                                  *
472  *                                                                            *
473  * Purpose: close trap file and reset lastsize                                *
474  *                                                                            *
475  * Author: Rudolfs Kreicbergs                                                 *
476  *                                                                            *
477  * Comments: !!! do not reset lastsize elsewhere !!!                          *
478  *                                                                            *
479  ******************************************************************************/
close_trap_file(void)480 static void	close_trap_file(void)
481 {
482 	if (-1 != trap_fd)
483 		close(trap_fd);
484 
485 	trap_fd = -1;
486 	trap_lastsize = 0;
487 	DBupdate_lastsize();
488 }
489 
490 /******************************************************************************
491  *                                                                            *
492  * Function: open_trap_file                                                   *
493  *                                                                            *
494  * Purpose: open the trap file and get it's node number                       *
495  *                                                                            *
496  * Return value: file descriptor of the opened file or -1 otherwise           *
497  *                                                                            *
498  * Author: Rudolfs Kreicbergs                                                 *
499  *                                                                            *
500  ******************************************************************************/
open_trap_file(void)501 static int	open_trap_file(void)
502 {
503 	zbx_stat_t	file_buf;
504 	char		*error = NULL;
505 
506 	if (-1 == (trap_fd = open(CONFIG_SNMPTRAP_FILE, O_RDONLY)))
507 	{
508 		if (ENOENT != errno)	/* file exists but cannot be opened */
509 		{
510 			error = zbx_dsprintf(error, "cannot open SNMP trapper file \"%s\": %s",
511 					CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
512 			delay_trap_logs(error, LOG_LEVEL_CRIT);
513 		}
514 		goto out;
515 	}
516 
517 	if (0 != zbx_fstat(trap_fd, &file_buf))
518 	{
519 		error = zbx_dsprintf(error, "cannot stat SNMP trapper file \"%s\": %s", CONFIG_SNMPTRAP_FILE,
520 				zbx_strerror(errno));
521 		delay_trap_logs(error, LOG_LEVEL_CRIT);
522 		close(trap_fd);
523 		trap_fd = -1;
524 		goto out;
525 	}
526 
527 	trap_ino = file_buf.st_ino;	/* a new file was opened */
528 out:
529 	zbx_free(error);
530 
531 	return trap_fd;
532 }
533 
534 /******************************************************************************
535  *                                                                            *
536  * Function: get_latest_data                                                  *
537  *                                                                            *
538  * Purpose: Open the latest trap file. If the current file has been rotated,  *
539  *          process that and then open the latest file.                       *
540  *                                                                            *
541  * Return value: SUCCEED - there are new traps to be parsed                   *
542  *               FAIL - there are no new traps or trap file does not exist    *
543  *                                                                            *
544  * Author: Rudolfs Kreicbergs                                                 *
545  *                                                                            *
546  ******************************************************************************/
get_latest_data(void)547 static int	get_latest_data(void)
548 {
549 	zbx_stat_t	file_buf;
550 
551 	if (-1 != trap_fd)	/* a trap file is already open */
552 	{
553 		if (0 != zbx_stat(CONFIG_SNMPTRAP_FILE, &file_buf))
554 		{
555 			/* file might have been renamed or deleted, process the current file */
556 
557 			if (ENOENT != errno)
558 			{
559 				zabbix_log(LOG_LEVEL_CRIT, "cannot stat SNMP trapper file \"%s\": %s",
560 						CONFIG_SNMPTRAP_FILE, zbx_strerror(errno));
561 			}
562 
563 			while (0 < read_traps())
564 				;
565 
566 			if (0 != offset)
567 				parse_traps(1);
568 
569 			close_trap_file();
570 		}
571 		else if (file_buf.st_ino != trap_ino || file_buf.st_size < trap_lastsize)
572 		{
573 			/* file has been rotated, process the current file */
574 
575 			while (0 < read_traps())
576 				;
577 
578 			if (0 != offset)
579 				parse_traps(1);
580 
581 			close_trap_file();
582 		}
583 		/* in case when file access permission is changed and read permission is denied */
584 		else if (0 != access(CONFIG_SNMPTRAP_FILE, R_OK))
585 		{
586 			if (EACCES == errno)
587 				close_trap_file();
588 		}
589 		else if (file_buf.st_size == trap_lastsize)
590 		{
591 			if (1 == force)
592 			{
593 				parse_traps(1);
594 				force = 0;
595 			}
596 			else if (0 != offset && 0 == force)
597 			{
598 				force = 1;
599 			}
600 
601 			return FAIL;	/* no new traps */
602 		}
603 	}
604 
605 	force = 0;
606 
607 	if (-1 == trap_fd && -1 == open_trap_file())
608 		return FAIL;
609 
610 	return SUCCEED;
611 }
612 
613 /******************************************************************************
614  *                                                                            *
615  * Function: snmptrapper_thread                                               *
616  *                                                                            *
617  * Purpose: SNMP trap reader's entry point                                    *
618  *                                                                            *
619  * Author: Rudolfs Kreicbergs                                                 *
620  *                                                                            *
621  ******************************************************************************/
ZBX_THREAD_ENTRY(snmptrapper_thread,args)622 ZBX_THREAD_ENTRY(snmptrapper_thread, args)
623 {
624 	const char	*__function_name = "snmptrapper_thread";
625 	double		sec;
626 
627 	process_type = ((zbx_thread_args_t *)args)->process_type;
628 	server_num = ((zbx_thread_args_t *)args)->server_num;
629 	process_num = ((zbx_thread_args_t *)args)->process_num;
630 
631 	zabbix_log(LOG_LEVEL_INFORMATION, "%s #%d started [%s #%d]", get_program_type_string(program_type),
632 			server_num, get_process_type_string(process_type), process_num);
633 
634 	update_selfmon_counter(ZBX_PROCESS_STATE_BUSY);
635 
636 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() trapfile:'%s'", __function_name, CONFIG_SNMPTRAP_FILE);
637 
638 	zbx_setproctitle("%s [connecting to the database]", get_process_type_string(process_type));
639 
640 	DBconnect(ZBX_DB_CONNECT_NORMAL);
641 
642 	DBget_lastsize();
643 
644 	buffer = (char *)zbx_malloc(buffer, MAX_BUFFER_LEN);
645 	*buffer = '\0';
646 
647 	while (ZBX_IS_RUNNING())
648 	{
649 		sec = zbx_time();
650 		zbx_update_env(sec);
651 
652 		zbx_setproctitle("%s [processing data]", get_process_type_string(process_type));
653 
654 		while (ZBX_IS_RUNNING() && SUCCEED == get_latest_data())
655 			read_traps();
656 		sec = zbx_time() - sec;
657 
658 		zbx_setproctitle("%s [processed data in " ZBX_FS_DBL " sec, idle 1 sec]",
659 				get_process_type_string(process_type), sec);
660 
661 		zbx_sleep_loop(1);
662 	}
663 
664 	zbx_free(buffer);
665 
666 	if (-1 != trap_fd)
667 		close(trap_fd);
668 
669 	zbx_setproctitle("%s #%d [terminated]", get_process_type_string(process_type), process_num);
670 
671 	while (1)
672 		zbx_sleep(SEC_PER_MIN);
673 }
674