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 "cfg.h"
22 #include "log.h"
23 
24 extern unsigned char	program_type;
25 
26 char	*CONFIG_FILE		= NULL;
27 
28 char	*CONFIG_LOG_TYPE_STR	= NULL;
29 int	CONFIG_LOG_TYPE		= LOG_TYPE_UNDEFINED;
30 char	*CONFIG_LOG_FILE	= NULL;
31 int	CONFIG_LOG_FILE_SIZE	= 1;
32 int	CONFIG_ALLOW_ROOT	= 0;
33 int	CONFIG_TIMEOUT		= 3;
34 
35 static int	__parse_cfg_file(const char *cfg_file, struct cfg_line *cfg, int level, int optional, int strict);
36 
37 /******************************************************************************
38  *                                                                            *
39  * Function: match_glob                                                       *
40  *                                                                            *
41  * Purpose: see whether a file (e.g., "parameter.conf")                       *
42  *          matches a pattern (e.g., "p*.conf")                               *
43  *                                                                            *
44  * Return value: SUCCEED - file matches a pattern                             *
45  *               FAIL - otherwise                                             *
46  *                                                                            *
47  ******************************************************************************/
match_glob(const char * file,const char * pattern)48 static int	match_glob(const char *file, const char *pattern)
49 {
50 	const char	*f, *g, *p, *q;
51 
52 	f = file;
53 	p = pattern;
54 
55 	while (1)
56 	{
57 		/* corner case */
58 
59 		if ('\0' == *p)
60 			return '\0' == *f ? SUCCEED : FAIL;
61 
62 		/* find a set of literal characters */
63 
64 		while ('*' == *p)
65 			p++;
66 
67 		for (q = p; '\0' != *q && '*' != *q; q++)
68 			;
69 
70 		/* if literal characters are at the beginning... */
71 
72 		if (pattern == p)
73 		{
74 #ifdef _WINDOWS
75 			if (0 != zbx_strncasecmp(f, p, q - p))
76 #else
77 			if (0 != strncmp(f, p, q - p))
78 #endif
79 				return FAIL;
80 
81 			f += q - p;
82 			p = q;
83 
84 			continue;
85 		}
86 
87 		/* if literal characters are at the end... */
88 
89 		if ('\0' == *q)
90 		{
91 			for (g = f; '\0' != *g; g++)
92 				;
93 
94 			if (g - f < q - p)
95 				return FAIL;
96 #ifdef _WINDOWS
97 			return 0 == strcasecmp(g - (q - p), p) ? SUCCEED : FAIL;
98 #else
99 			return 0 == strcmp(g - (q - p), p) ? SUCCEED : FAIL;
100 #endif
101 		}
102 
103 		/* if literal characters are in the middle... */
104 
105 		while (1)
106 		{
107 			if ('\0' == *f)
108 				return FAIL;
109 #ifdef _WINDOWS
110 			if (0 == zbx_strncasecmp(f, p, q - p))
111 #else
112 			if (0 == strncmp(f, p, q - p))
113 #endif
114 			{
115 				f += q - p;
116 				p = q;
117 
118 				break;
119 			}
120 
121 			f++;
122 		}
123 	}
124 }
125 
126 /******************************************************************************
127  *                                                                            *
128  * Function: parse_glob                                                       *
129  *                                                                            *
130  * Purpose: parse a glob like "/usr/local/etc/zabbix_agentd.conf.d/p*.conf"   *
131  *          into "/usr/local/etc/zabbix_agentd.conf.d" and "p*.conf" parts    *
132  *                                                                            *
133  * Parameters: glob    - [IN] glob as specified in Include directive          *
134  *             path    - [OUT] parsed path, either directory or file          *
135  *             pattern - [OUT] parsed pattern, if path is directory           *
136  *                                                                            *
137  * Return value: SUCCEED - glob is valid and was parsed successfully          *
138  *               FAIL - otherwise                                             *
139  *                                                                            *
140  ******************************************************************************/
parse_glob(const char * glob,char ** path,char ** pattern)141 static int	parse_glob(const char *glob, char **path, char **pattern)
142 {
143 	const char	*p;
144 
145 	if (NULL == (p = strchr(glob, '*')))
146 	{
147 		*path = zbx_strdup(NULL, glob);
148 		*pattern = NULL;
149 
150 		goto trim;
151 	}
152 
153 	if (NULL != strchr(p + 1, PATH_SEPARATOR))
154 	{
155 		zbx_error("%s: glob pattern should be the last component of the path", glob);
156 		return FAIL;
157 	}
158 
159 	do
160 	{
161 		if (glob == p)
162 		{
163 			zbx_error("%s: path should be absolute", glob);
164 			return FAIL;
165 		}
166 
167 		p--;
168 	}
169 	while (PATH_SEPARATOR != *p);
170 
171 	*path = zbx_strdup(NULL, glob);
172 	(*path)[p - glob] = '\0';
173 
174 	*pattern = zbx_strdup(NULL, p + 1);
175 trim:
176 #ifdef _WINDOWS
177 	if (0 != zbx_rtrim(*path, "\\") && NULL == *pattern)
178 		*pattern = zbx_strdup(NULL, "*");			/* make sure path is a directory */
179 
180 	if (':' == (*path)[1] && '\0' == (*path)[2] && '\\' == glob[2])	/* retain backslash for "C:\" */
181 	{
182 		(*path)[2] = '\\';
183 		(*path)[3] = '\0';
184 	}
185 #else
186 	if (0 != zbx_rtrim(*path, "/") && NULL == *pattern)
187 		*pattern = zbx_strdup(NULL, "*");			/* make sure path is a directory */
188 
189 	if ('\0' == (*path)[0] && '/' == glob[0])			/* retain forward slash for "/" */
190 	{
191 		(*path)[0] = '/';
192 		(*path)[1] = '\0';
193 	}
194 #endif
195 	return SUCCEED;
196 }
197 
198 /******************************************************************************
199  *                                                                            *
200  * Function: parse_cfg_dir                                                    *
201  *                                                                            *
202  * Purpose: parse directory with configuration files                          *
203  *                                                                            *
204  * Parameters: path    - full path to directory                               *
205  *             pattern - pattern that files in the directory should match     *
206  *             cfg     - pointer to configuration parameter structure         *
207  *             level   - a level of included file                             *
208  *             strict  - treat unknown parameters as error                    *
209  *                                                                            *
210  * Return value: SUCCEED - parsed successfully                                *
211  *               FAIL - error processing directory                            *
212  *                                                                            *
213  ******************************************************************************/
214 #ifdef _WINDOWS
parse_cfg_dir(const char * path,const char * pattern,struct cfg_line * cfg,int level,int strict)215 static int	parse_cfg_dir(const char *path, const char *pattern, struct cfg_line *cfg, int level, int strict)
216 {
217 	WIN32_FIND_DATAW	find_file_data;
218 	HANDLE			h_find;
219 	char 			*find_path = NULL, *file = NULL, *file_name;
220 	wchar_t			*wfind_path = NULL;
221 	int			ret = FAIL;
222 
223 	find_path = zbx_dsprintf(find_path, "%s\\*", path);
224 	wfind_path = zbx_utf8_to_unicode(find_path);
225 
226 	if (INVALID_HANDLE_VALUE == (h_find = FindFirstFileW(wfind_path, &find_file_data)))
227 		goto clean;
228 
229 	while (0 != FindNextFileW(h_find, &find_file_data))
230 	{
231 		if (0 != (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
232 			continue;
233 
234 		file_name = zbx_unicode_to_utf8(find_file_data.cFileName);
235 
236 		if (NULL != pattern && SUCCEED != match_glob(file_name, pattern))
237 		{
238 			zbx_free(file_name);
239 			continue;
240 		}
241 
242 		file = zbx_dsprintf(file, "%s\\%s", path, file_name);
243 
244 		zbx_free(file_name);
245 
246 		if (SUCCEED != __parse_cfg_file(file, cfg, level, ZBX_CFG_FILE_REQUIRED, strict))
247 			goto close;
248 	}
249 
250 	ret = SUCCEED;
251 close:
252 	zbx_free(file);
253 	FindClose(h_find);
254 clean:
255 	zbx_free(wfind_path);
256 	zbx_free(find_path);
257 
258 	return ret;
259 }
260 #else
parse_cfg_dir(const char * path,const char * pattern,struct cfg_line * cfg,int level,int strict)261 static int	parse_cfg_dir(const char *path, const char *pattern, struct cfg_line *cfg, int level, int strict)
262 {
263 	DIR		*dir;
264 	struct dirent	*d;
265 	zbx_stat_t	sb;
266 	char		*file = NULL;
267 	int		ret = FAIL;
268 
269 	if (NULL == (dir = opendir(path)))
270 	{
271 		zbx_error("%s: %s", path, zbx_strerror(errno));
272 		goto out;
273 	}
274 
275 	while (NULL != (d = readdir(dir)))
276 	{
277 		file = zbx_dsprintf(file, "%s/%s", path, d->d_name);
278 
279 		if (0 != zbx_stat(file, &sb) || 0 == S_ISREG(sb.st_mode))
280 			continue;
281 
282 		if (NULL != pattern && SUCCEED != match_glob(d->d_name, pattern))
283 			continue;
284 
285 		if (SUCCEED != __parse_cfg_file(file, cfg, level, ZBX_CFG_FILE_REQUIRED, strict))
286 			goto close;
287 	}
288 
289 	ret = SUCCEED;
290 close:
291 	if (0 != closedir(dir))
292 	{
293 		zbx_error("%s: %s", path, zbx_strerror(errno));
294 		ret = FAIL;
295 	}
296 
297 	zbx_free(file);
298 out:
299 	return ret;
300 }
301 #endif
302 
303 /******************************************************************************
304  *                                                                            *
305  * Function: parse_cfg_object                                                 *
306  *                                                                            *
307  * Purpose: parse "Include=..." line in configuration file                    *
308  *                                                                            *
309  * Parameters: cfg_file - full name of config file                            *
310  *             cfg      - pointer to configuration parameter structure        *
311  *             level    - a level of included file                            *
312  *             strict   - treat unknown parameters as error                   *
313  *                                                                            *
314  * Return value: SUCCEED - parsed successfully                                *
315  *               FAIL - error processing object                               *
316  *                                                                            *
317  ******************************************************************************/
parse_cfg_object(const char * cfg_file,struct cfg_line * cfg,int level,int strict)318 static int	parse_cfg_object(const char *cfg_file, struct cfg_line *cfg, int level, int strict)
319 {
320 	int		ret = FAIL;
321 	char		*path = NULL, *pattern = NULL;
322 	zbx_stat_t	sb;
323 
324 	if (SUCCEED != parse_glob(cfg_file, &path, &pattern))
325 		goto clean;
326 
327 	if (0 != zbx_stat(path, &sb))
328 	{
329 		zbx_error("%s: %s", path, zbx_strerror(errno));
330 		goto clean;
331 	}
332 
333 	if (0 == S_ISDIR(sb.st_mode))
334 	{
335 		if (NULL == pattern)
336 		{
337 			ret = __parse_cfg_file(path, cfg, level, ZBX_CFG_FILE_REQUIRED, strict);
338 			goto clean;
339 		}
340 
341 		zbx_error("%s: base path is not a directory", cfg_file);
342 		goto clean;
343 	}
344 
345 	ret = parse_cfg_dir(path, pattern, cfg, level, strict);
346 clean:
347 	zbx_free(pattern);
348 	zbx_free(path);
349 
350 	return ret;
351 }
352 
353 /******************************************************************************
354  *                                                                            *
355  * Function: parse_cfg_file                                                   *
356  *                                                                            *
357  * Purpose: parse configuration file                                          *
358  *                                                                            *
359  * Parameters: cfg_file - full name of config file                            *
360  *             cfg      - pointer to configuration parameter structure        *
361  *             level    - a level of included file                            *
362  *             optional - do not treat missing configuration file as error    *
363  *             strict   - treat unknown parameters as error                   *
364  *                                                                            *
365  * Return value: SUCCEED - parsed successfully                                *
366  *               FAIL - error processing config file                          *
367  *                                                                            *
368  * Author: Alexei Vladishev, Eugene Grigorjev                                 *
369  *                                                                            *
370  ******************************************************************************/
__parse_cfg_file(const char * cfg_file,struct cfg_line * cfg,int level,int optional,int strict)371 static int	__parse_cfg_file(const char *cfg_file, struct cfg_line *cfg, int level, int optional, int strict)
372 {
373 #define ZBX_MAX_INCLUDE_LEVEL	10
374 
375 #define ZBX_CFG_LTRIM_CHARS	"\t "
376 #define ZBX_CFG_RTRIM_CHARS	ZBX_CFG_LTRIM_CHARS "\r\n"
377 
378 	FILE		*file;
379 	int		i, lineno, param_valid;
380 	char		line[MAX_STRING_LEN + 3], *parameter, *value;
381 	zbx_uint64_t	var;
382 	size_t		len;
383 #ifdef _WINDOWS
384 	wchar_t		*wcfg_file;
385 #endif
386 	if (++level > ZBX_MAX_INCLUDE_LEVEL)
387 	{
388 		zbx_error("Recursion detected! Skipped processing of '%s'.", cfg_file);
389 		return FAIL;
390 	}
391 
392 	if (NULL != cfg_file)
393 	{
394 #ifdef _WINDOWS
395 		wcfg_file = zbx_utf8_to_unicode(cfg_file);
396 		file = _wfopen(wcfg_file, L"r");
397 		zbx_free(wcfg_file);
398 
399 		if (NULL == file)
400 			goto cannot_open;
401 #else
402 		if (NULL == (file = fopen(cfg_file, "r")))
403 			goto cannot_open;
404 #endif
405 		for (lineno = 1; NULL != fgets(line, sizeof(line), file); lineno++)
406 		{
407 			/* check if line length exceeds limit (max. 2048 bytes) */
408 			len = strlen(line);
409 			if (MAX_STRING_LEN < len && NULL == strchr("\r\n", line[MAX_STRING_LEN]))
410 				goto line_too_long;
411 
412 			zbx_ltrim(line, ZBX_CFG_LTRIM_CHARS);
413 			zbx_rtrim(line, ZBX_CFG_RTRIM_CHARS);
414 
415 			if ('#' == *line || '\0' == *line)
416 				continue;
417 
418 			/* we only support UTF-8 characters in the config file */
419 			if (SUCCEED != zbx_is_utf8(line))
420 				goto non_utf8;
421 
422 			parameter = line;
423 			if (NULL == (value = strchr(line, '=')))
424 				goto non_key_value;
425 
426 			*value++ = '\0';
427 
428 			zbx_rtrim(parameter, ZBX_CFG_RTRIM_CHARS);
429 			zbx_ltrim(value, ZBX_CFG_LTRIM_CHARS);
430 
431 			zabbix_log(LOG_LEVEL_DEBUG, "cfg: para: [%s] val [%s]", parameter, value);
432 
433 			if (0 == strcmp(parameter, "Include"))
434 			{
435 				if (FAIL == parse_cfg_object(value, cfg, level, strict))
436 				{
437 					fclose(file);
438 					goto error;
439 				}
440 
441 				continue;
442 			}
443 
444 			param_valid = 0;
445 
446 			for (i = 0; NULL != cfg[i].parameter; i++)
447 			{
448 				if (0 != strcmp(cfg[i].parameter, parameter))
449 					continue;
450 
451 				param_valid = 1;
452 
453 				zabbix_log(LOG_LEVEL_DEBUG, "accepted configuration parameter: '%s' = '%s'",
454 						parameter, value);
455 
456 				switch (cfg[i].type)
457 				{
458 					case TYPE_INT:
459 						if (FAIL == str2uint64(value, "KMGT", &var))
460 							goto incorrect_config;
461 
462 						if (cfg[i].min > var || (0 != cfg[i].max && var > cfg[i].max))
463 							goto incorrect_config;
464 
465 						*((int *)cfg[i].variable) = (int)var;
466 						break;
467 					case TYPE_STRING_LIST:
468 						zbx_trim_str_list(value, ',');
469 						/* break; is not missing here */
470 					case TYPE_STRING:
471 						*((char **)cfg[i].variable) =
472 								zbx_strdup(*((char **)cfg[i].variable), value);
473 						break;
474 					case TYPE_MULTISTRING:
475 						zbx_strarr_add(cfg[i].variable, value);
476 						break;
477 					case TYPE_UINT64:
478 						if (FAIL == str2uint64(value, "KMGT", &var))
479 							goto incorrect_config;
480 
481 						if (cfg[i].min > var || (0 != cfg[i].max && var > cfg[i].max))
482 							goto incorrect_config;
483 
484 						*((zbx_uint64_t *)cfg[i].variable) = var;
485 						break;
486 					default:
487 						assert(0);
488 				}
489 			}
490 
491 			if (0 == param_valid && ZBX_CFG_STRICT == strict)
492 				goto unknown_parameter;
493 		}
494 		fclose(file);
495 	}
496 
497 	if (1 != level)	/* skip mandatory parameters check for included files */
498 		return SUCCEED;
499 
500 	for (i = 0; NULL != cfg[i].parameter; i++) /* check for mandatory parameters */
501 	{
502 		if (PARM_MAND != cfg[i].mandatory)
503 			continue;
504 
505 		switch (cfg[i].type)
506 		{
507 			case TYPE_INT:
508 				if (0 == *((int *)cfg[i].variable))
509 					goto missing_mandatory;
510 				break;
511 			case TYPE_STRING:
512 			case TYPE_STRING_LIST:
513 				if (NULL == (*(char **)cfg[i].variable))
514 					goto missing_mandatory;
515 				break;
516 			default:
517 				assert(0);
518 		}
519 	}
520 
521 	return SUCCEED;
522 cannot_open:
523 	if (0 != optional)
524 		return SUCCEED;
525 	zbx_error("cannot open config file \"%s\": %s", cfg_file, zbx_strerror(errno));
526 	goto error;
527 line_too_long:
528 	fclose(file);
529 	zbx_error("line %d exceeds %d byte length limit in config file \"%s\"", lineno, MAX_STRING_LEN, cfg_file);
530 	goto error;
531 non_utf8:
532 	fclose(file);
533 	zbx_error("non-UTF-8 character at line %d \"%s\" in config file \"%s\"", lineno, line, cfg_file);
534 	goto error;
535 non_key_value:
536 	fclose(file);
537 	zbx_error("invalid entry \"%s\" (not following \"parameter=value\" notation) in config file \"%s\", line %d",
538 			line, cfg_file, lineno);
539 	goto error;
540 incorrect_config:
541 	fclose(file);
542 	zbx_error("wrong value of \"%s\" in config file \"%s\", line %d", cfg[i].parameter, cfg_file, lineno);
543 	goto error;
544 unknown_parameter:
545 	fclose(file);
546 	zbx_error("unknown parameter \"%s\" in config file \"%s\", line %d", parameter, cfg_file, lineno);
547 	goto error;
548 missing_mandatory:
549 	zbx_error("missing mandatory parameter \"%s\" in config file \"%s\"", cfg[i].parameter, cfg_file);
550 error:
551 	exit(EXIT_FAILURE);
552 }
553 
parse_cfg_file(const char * cfg_file,struct cfg_line * cfg,int optional,int strict)554 int	parse_cfg_file(const char *cfg_file, struct cfg_line *cfg, int optional, int strict)
555 {
556 	return __parse_cfg_file(cfg_file, cfg, 0, optional, strict);
557 }
558 
check_cfg_feature_int(const char * parameter,int value,const char * feature)559 int	check_cfg_feature_int(const char *parameter, int value, const char *feature)
560 {
561 	if (0 != value)
562 	{
563 		zbx_error("\"%s\" configuration parameter cannot be used: Zabbix %s was compiled without %s",
564 				parameter, get_program_type_string(program_type), feature);
565 		return FAIL;
566 	}
567 
568 	return SUCCEED;
569 }
570 
check_cfg_feature_str(const char * parameter,const char * value,const char * feature)571 int	check_cfg_feature_str(const char *parameter, const char *value, const char *feature)
572 {
573 	if (NULL != value)
574 	{
575 		zbx_error("\"%s\" configuration parameter cannot be used: Zabbix %s was compiled without %s",
576 				parameter, get_program_type_string(program_type), feature);
577 		return FAIL;
578 	}
579 
580 	return SUCCEED;
581 }
582