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, 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].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(&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 	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