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(&regexps);
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(&regexps, 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(&regexps, 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(&regexps);
223 	zbx_vector_ptr_destroy(&regexps);
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