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(&regexps);
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(&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 (SUCCEED != regexp_match_ex(&regexps, 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(&regexps);
220 	zbx_vector_ptr_destroy(&regexps);
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