/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see .
**/
#include "zbxcfg.h"
#include "zbxstr.h"
#include "zbxip.h"
#include "zbxfile.h"
#if defined(_WINDOWS) || defined(__MINGW32__)
# include "zbxwin32.h"
#endif
#if defined(_WINDOWS) || defined(__MINGW32__)
#include
#else
#include
#endif
static const char *program_type_str = NULL;
static const char *main_cfg_file = NULL;
static int __parse_cfg_file(const char *cfg_file, zbx_cfg_line_t *cfg, int level, int optional, int strict,
int noexit);
ZBX_PTR_VECTOR_IMPL(addr_ptr, zbx_addr_t *)
void zbx_init_library_cfg(unsigned char program_type, const char *cfg_file)
{
program_type_str = get_program_type_string(program_type);
main_cfg_file = cfg_file;
}
/******************************************************************************
* *
* Purpose: Checks whether a file (e.g., "parameter.conf") *
* matches a pattern (e.g., "p*.conf"). *
* *
* Return value: SUCCEED - file matches pattern *
* FAIL - otherwise *
* *
******************************************************************************/
static int match_glob(const char *file, const char *pattern)
{
const char *f, *g, *p, *q;
f = file;
p = pattern;
while (1)
{
/* corner case */
if ('\0' == *p)
return '\0' == *f ? SUCCEED : FAIL;
/* find a set of literal characters */
while ('*' == *p)
p++;
for (q = p; '\0' != *q && '*' != *q; q++)
;
/* if literal characters are at the beginning... */
if (pattern == p)
{
#ifdef _WINDOWS
if (0 != zbx_strncasecmp(f, p, q - p))
#else
if (0 != strncmp(f, p, q - p))
#endif
return FAIL;
f += q - p;
p = q;
continue;
}
/* if literal characters are at the end... */
if ('\0' == *q)
{
for (g = f; '\0' != *g; g++)
;
if (g - f < q - p)
return FAIL;
#ifdef _WINDOWS
return 0 == strcasecmp(g - (q - p), p) ? SUCCEED : FAIL;
#else
return 0 == strcmp(g - (q - p), p) ? SUCCEED : FAIL;
#endif
}
/* if literal characters are in the middle... */
while (1)
{
if ('\0' == *f)
return FAIL;
#ifdef _WINDOWS
if (0 == zbx_strncasecmp(f, p, q - p))
#else
if (0 == strncmp(f, p, q - p))
#endif
{
f += q - p;
p = q;
break;
}
f++;
}
}
}
/******************************************************************************
* *
* Purpose: Parses a glob like "/usr/local/etc/zabbix_agentd.conf.d/p*.conf" *
* into "/usr/local/etc/zabbix_agentd.conf.d" and "p*.conf" parts. *
* *
* Parameters: glob - [IN] glob as specified in Include directive *
* path - [OUT] parsed path, either directory or file *
* pattern - [OUT] parsed pattern, if path is directory *
* *
* Return value: SUCCEED - glob is valid and was parsed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
static int parse_glob(const char *glob, char **path, char **pattern)
{
const char *p;
if (NULL == (p = strchr(glob, '*')))
{
*path = zbx_strdup(NULL, glob);
*pattern = NULL;
goto trim;
}
if (NULL != strchr(p + 1, ZBX_PATH_SEPARATOR))
{
zbx_error("%s: glob pattern should be the last component of the path", glob);
return FAIL;
}
do
{
if (glob == p)
{
zbx_error("%s: path should be absolute", glob);
return FAIL;
}
p--;
}
while (ZBX_PATH_SEPARATOR != *p);
*path = zbx_strdup(NULL, glob);
(*path)[p - glob] = '\0';
*pattern = zbx_strdup(NULL, p + 1);
trim:
#ifdef _WINDOWS
if (0 != zbx_rtrim(*path, "\\") && NULL == *pattern)
*pattern = zbx_strdup(NULL, "*"); /* make sure path is a directory */
if (':' == (*path)[1] && '\0' == (*path)[2] && '\\' == glob[2]) /* retain backslash for "C:\" */
{
(*path)[2] = '\\';
(*path)[3] = '\0';
}
#else
if (0 != zbx_rtrim(*path, "/") && NULL == *pattern)
*pattern = zbx_strdup(NULL, "*"); /* make sure path is a directory */
if ('\0' == (*path)[0] && '/' == glob[0]) /* retain forward slash for "/" */
{
(*path)[0] = '/';
(*path)[1] = '\0';
}
#endif
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: parses directory with configuration files *
* *
* Parameters: path - [IN] full path to directory *
* pattern - [IN] pattern that files in directory should match *
* cfg - [OUT] pointer to configuration parameter structure *
* level - [IN] level of included file *
* strict - [IN] treat unknown parameters as error *
* noexit - [INT] on error return FAIL instead of EXIT_FAILURE *
* *
* Return value: SUCCEED - parsed successfully *
* FAIL - error processing directory *
* *
******************************************************************************/
#ifdef _WINDOWS
static int parse_cfg_dir(const char *path, const char *pattern, zbx_cfg_line_t *cfg, int level, int strict,
int noexit)
{
WIN32_FIND_DATAW find_file_data;
HANDLE h_find;
char *file = NULL, *file_name, *find_path;
wchar_t *wfind_path = NULL;
int ret = FAIL;
find_path = zbx_dsprintf(NULL, "%s\\*", path);
wfind_path = zbx_utf8_to_unicode(find_path);
if (INVALID_HANDLE_VALUE == (h_find = FindFirstFileW(wfind_path, &find_file_data)))
goto clean;
while (0 != FindNextFileW(h_find, &find_file_data))
{
if (0 != (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
continue;
file_name = zbx_unicode_to_utf8(find_file_data.cFileName);
if (NULL != pattern && SUCCEED != match_glob(file_name, pattern))
{
zbx_free(file_name);
continue;
}
file = zbx_dsprintf(file, "%s\\%s", path, file_name);
zbx_free(file_name);
if (SUCCEED != __parse_cfg_file(file, cfg, level, ZBX_CFG_FILE_REQUIRED, strict, noexit))
goto close;
}
ret = SUCCEED;
close:
zbx_free(file);
FindClose(h_find);
clean:
zbx_free(wfind_path);
zbx_free(find_path);
return ret;
}
#else
static int parse_cfg_dir(const char *path, const char *pattern, zbx_cfg_line_t *cfg, int level, int strict,
int noexit)
{
DIR *dir;
struct dirent *d;
zbx_stat_t sb;
char *file = NULL;
int ret = FAIL;
if (NULL == (dir = opendir(path)))
{
zbx_error("%s: %s", path, zbx_strerror(errno));
goto out;
}
while (NULL != (d = readdir(dir)))
{
file = zbx_dsprintf(file, "%s/%s", path, d->d_name);
if (0 != zbx_stat(file, &sb) || 0 == S_ISREG(sb.st_mode))
continue;
if (NULL != pattern && SUCCEED != match_glob(d->d_name, pattern))
continue;
if (SUCCEED != __parse_cfg_file(file, cfg, level, ZBX_CFG_FILE_REQUIRED, strict, noexit))
goto close;
}
ret = SUCCEED;
close:
if (0 != closedir(dir))
{
zbx_error("%s: %s", path, zbx_strerror(errno));
ret = FAIL;
}
zbx_free(file);
out:
return ret;
}
#endif
static char *expand_include_path(char *raw_path)
{
#if defined(_WINDOWS) || defined(__MINGW32__)
wchar_t *wraw_path;
wraw_path = zbx_utf8_to_unicode(raw_path);
if (TRUE == PathIsRelativeW(wraw_path))
{
wchar_t *wconfig_path, dir_buf[_MAX_DIR];
char *dir_utf8, *result = NULL;
zbx_free(wraw_path);
wconfig_path = zbx_utf8_to_unicode(main_cfg_file);
_wsplitpath(wconfig_path, NULL, dir_buf, NULL, NULL);
zbx_free(wconfig_path);
dir_utf8 = zbx_unicode_to_utf8(dir_buf);
result = zbx_dsprintf(result, "%s%s", dir_utf8, raw_path);
zbx_free(raw_path);
zbx_free(dir_utf8);
return result;
}
zbx_free(wraw_path);
#else
if ('/' != *raw_path)
{
char *cfg_file, *path;
cfg_file = zbx_strdup(NULL, main_cfg_file);
path = zbx_dsprintf(NULL, "%s/%s", dirname(cfg_file), raw_path);
zbx_free(cfg_file);
zbx_free(raw_path);
return path;
}
#endif
return raw_path;
}
/******************************************************************************
* *
* Purpose: parses "Include=..." line in configuration file *
* *
* Parameters: cfg_file - [IN] full name of config file *
* cfg - [OUT] pointer to configuration parameter structure *
* level - [IN] level of included file *
* strict - [IN] treat unknown parameters as error *
* noexit - [IN] on error return FAIL instead of EXIT_FAILURE *
* *
* Return value: SUCCEED - parsed successfully *
* FAIL - error processing object *
* *
******************************************************************************/
static int parse_cfg_object(const char *cfg_file, zbx_cfg_line_t *cfg, int level, int strict, int noexit)
{
int ret = FAIL;
char *path = NULL, *pattern = NULL;
zbx_stat_t sb;
if (SUCCEED != parse_glob(cfg_file, &path, &pattern))
goto clean;
path = expand_include_path(path);
if (0 != zbx_stat(path, &sb))
{
zbx_error("%s: %s", path, zbx_strerror(errno));
goto clean;
}
if (0 == S_ISDIR(sb.st_mode))
{
if (NULL == pattern)
{
ret = __parse_cfg_file(path, cfg, level, ZBX_CFG_FILE_REQUIRED, strict, noexit);
goto clean;
}
zbx_error("%s: base path is not a directory", cfg_file);
goto clean;
}
ret = parse_cfg_dir(path, pattern, cfg, level, strict, noexit);
clean:
zbx_free(pattern);
zbx_free(path);
return ret;
}
/********************************************************************************
* *
* Purpose: parses configuration file *
* *
* Parameters: cfg_file - [IN] full name of config file *
* cfg - [OUT] pointer to configuration parameter structure *
* level - [IN] level of included file *
* optional - [IN] do not treat missing configuration file as error *
* strict - [IN] treat unknown parameters as error *
* noexit - [IN] on error return FAIL instead of EXIT_FAILURE *
* *
* Return value: SUCCEED - parsed successfully *
* FAIL - error processing config file *
* *
********************************************************************************/
static int __parse_cfg_file(const char *cfg_file, zbx_cfg_line_t *cfg, int level, int optional, int strict,
int noexit)
{
#define ZBX_MAX_INCLUDE_LEVEL 10
#define ZBX_CFG_LTRIM_CHARS "\t "
#define ZBX_CFG_RTRIM_CHARS ZBX_CFG_LTRIM_CHARS "\r\n"
FILE *file;
int i, lineno, param_valid;
char line[MAX_STRING_LEN + 3], *parameter, *value;
zbx_uint64_t var;
size_t len;
#ifdef _WINDOWS
wchar_t *wcfg_file;
#endif
if (++level > ZBX_MAX_INCLUDE_LEVEL)
{
zbx_error("Recursion detected! Skipped processing of '%s'.", cfg_file);
return FAIL;
}
if (NULL != cfg_file)
{
#ifdef _WINDOWS
wcfg_file = zbx_utf8_to_unicode(cfg_file);
file = _wfopen(wcfg_file, L"r");
zbx_free(wcfg_file);
if (NULL == file)
goto cannot_open;
#else
if (NULL == (file = fopen(cfg_file, "r")))
goto cannot_open;
#endif
for (lineno = 1; NULL != fgets(line, sizeof(line), file); lineno++)
{
/* check if line length exceeds limit (max. 2048 bytes) */
len = strlen(line);
if (MAX_STRING_LEN < len && NULL == strchr("\r\n", line[MAX_STRING_LEN]))
goto line_too_long;
zbx_ltrim(line, ZBX_CFG_LTRIM_CHARS);
zbx_rtrim(line, ZBX_CFG_RTRIM_CHARS);
if ('#' == *line || '\0' == *line)
continue;
/* we only support UTF-8 characters in the config file */
if (SUCCEED != zbx_is_utf8(line))
goto non_utf8;
parameter = line;
if (NULL == (value = strchr(line, '=')))
goto non_key_value;
*value++ = '\0';
zbx_rtrim(parameter, ZBX_CFG_RTRIM_CHARS);
zbx_ltrim(value, ZBX_CFG_LTRIM_CHARS);
zabbix_log(LOG_LEVEL_DEBUG, "cfg: param: [%s] val [%s]", parameter, value);
if (0 == strcmp(parameter, "Include"))
{
if (FAIL == parse_cfg_object(value, cfg, level, strict, noexit))
{
fclose(file);
goto error;
}
continue;
}
param_valid = 0;
for (i = 0; NULL != cfg[i].parameter; i++)
{
if (0 != strcmp(cfg[i].parameter, parameter))
continue;
param_valid = 1;
zabbix_log(LOG_LEVEL_DEBUG, "accepted configuration parameter: '%s' = '%s'",
parameter, value);
switch (cfg[i].type)
{
case ZBX_CFG_TYPE_INT:
if (FAIL == zbx_str2uint64(value, "KMGT", &var))
goto incorrect_config;
if (cfg[i].min > var || (0 != cfg[i].max && var > cfg[i].max))
goto incorrect_config;
*((int *)cfg[i].variable) = (int)var;
break;
case ZBX_CFG_TYPE_STRING_LIST:
zbx_trim_str_list(value, ',');
ZBX_FALLTHROUGH;
case ZBX_CFG_TYPE_STRING:
*((char **)cfg[i].variable) =
zbx_strdup(*((char **)cfg[i].variable), value);
break;
case ZBX_CFG_TYPE_MULTISTRING:
zbx_strarr_add((char ***)cfg[i].variable, value);
break;
case ZBX_CFG_TYPE_UINT64:
if (FAIL == zbx_str2uint64(value, "KMGT", &var))
goto incorrect_config;
if (cfg[i].min > var || (0 != cfg[i].max && var > cfg[i].max))
goto incorrect_config;
*((zbx_uint64_t *)cfg[i].variable) = var;
break;
case ZBX_CFG_TYPE_CUSTOM:
if (NULL != cfg[i].variable)
{
zbx_cfg_custom_parameter_parser_t *p =
(zbx_cfg_custom_parameter_parser_t*)
cfg[i].variable;
if (SUCCEED != p->cfg_custom_parameter_parser_func(value,
&cfg[i]))
{
goto incorrect_config;
}
continue;
}
break;
default:
zbx_this_should_never_happen_backtrace();
assert(0);
}
}
if (0 == param_valid && ZBX_CFG_STRICT == strict)
goto unknown_parameter;
}
fclose(file);
}
if (1 != level) /* skip mandatory parameters check for included files */
return SUCCEED;
for (i = 0; NULL != cfg[i].parameter; i++) /* check for mandatory parameters */
{
if (ZBX_CONF_PARM_MAND != cfg[i].mandatory)
continue;
switch (cfg[i].type)
{
case ZBX_CFG_TYPE_INT:
if (0 == *((int *)cfg[i].variable))
goto missing_mandatory;
break;
case ZBX_CFG_TYPE_STRING:
case ZBX_CFG_TYPE_STRING_LIST:
if (NULL == (*(char **)cfg[i].variable))
goto missing_mandatory;
break;
default:
zbx_this_should_never_happen_backtrace();
assert(0);
}
}
return SUCCEED;
cannot_open:
if (ZBX_CFG_FILE_REQUIRED != optional)
return SUCCEED;
zbx_error("cannot open config file \"%s\": %s", cfg_file, zbx_strerror(errno));
goto error;
line_too_long:
fclose(file);
zbx_error("line %d exceeds %d byte length limit in config file \"%s\"", lineno, MAX_STRING_LEN, cfg_file);
goto error;
non_utf8:
fclose(file);
zbx_error("non-UTF-8 character at line %d \"%s\" in config file \"%s\"", lineno, line, cfg_file);
goto error;
non_key_value:
fclose(file);
zbx_error("invalid entry \"%s\" (not following \"parameter=value\" notation) in config file \"%s\", line %d",
line, cfg_file, lineno);
goto error;
incorrect_config:
fclose(file);
zbx_error("wrong value of \"%s\" in config file \"%s\", line %d", cfg[i].parameter, cfg_file, lineno);
goto error;
unknown_parameter:
fclose(file);
zbx_error("unknown parameter \"%s\" in config file \"%s\", line %d", parameter, cfg_file, lineno);
goto error;
missing_mandatory:
zbx_error("missing mandatory parameter \"%s\" in config file \"%s\"", cfg[i].parameter, cfg_file);
error:
if (0 == noexit)
exit(EXIT_FAILURE);
return FAIL;
#undef ZBX_MAX_INCLUDE_LEVEL
#undef ZBX_CFG_LTRIM_CHARS
#undef ZBX_CFG_RTRIM_CHARS
}
int zbx_parse_cfg_file(const char *cfg_file, zbx_cfg_line_t *cfg, int optional, int strict, int noexit)
{
return __parse_cfg_file(cfg_file, cfg, 0, optional, strict, noexit);
}
int zbx_check_cfg_feature_int(const char *parameter, int value, const char *feature)
{
if (0 != value)
{
zbx_error("\"%s\" configuration parameter cannot be used: Zabbix %s was compiled without %s",
parameter, program_type_str, feature);
return FAIL;
}
return SUCCEED;
}
int zbx_check_cfg_feature_str(const char *parameter, const char *value, const char *feature)
{
if (NULL != value)
{
zbx_error("\"%s\" configuration parameter cannot be used: Zabbix %s was compiled without %s",
parameter, program_type_str, feature);
return FAIL;
}
return SUCCEED;
}
void zbx_addr_free(zbx_addr_t *addr)
{
zbx_free(addr->ip);
zbx_free(addr);
}
void zbx_addr_copy(zbx_vector_addr_ptr_t *addr_to, const zbx_vector_addr_ptr_t *addr_from)
{
for (int j = 0; j < addr_from->values_num; j++)
{
const zbx_addr_t *addr = addr_from->values[j];
zbx_addr_t *addr_ptr = zbx_malloc(NULL, sizeof(zbx_addr_t));
addr_ptr->ip = zbx_strdup(NULL, addr->ip);
addr_ptr->port = addr->port;
addr_ptr->revision = addr->revision;
zbx_vector_addr_ptr_append(addr_to, addr_ptr);
}
}
static int addr_compare_func(const void *d1, const void *d2)
{
const zbx_addr_t *a1 = *(const zbx_addr_t * const *)d1;
const zbx_addr_t *a2 = *(const zbx_addr_t * const *)d2;
ZBX_RETURN_IF_NOT_EQUAL(a1->port, a2->port);
return strcmp(a1->ip, a2->ip);
}
/******************************************************************************
* *
* Purpose: Parses "ServerActive' parameter value and set destination servers *
* using a callback function. *
* *
******************************************************************************/
int zbx_set_data_destination_hosts(const char *str, unsigned short port, const char *name,
add_serveractive_host_f cb, zbx_vector_str_t *hostnames, void *data, char **error)
{
char *r, *r_node;
zbx_vector_addr_ptr_t addrs, cluster_addrs;
int ret = SUCCEED;
zbx_vector_addr_ptr_create(&addrs);
zbx_vector_addr_ptr_create(&cluster_addrs);
do
{
if (NULL != (r = strchr(str, ',')))
*r = '\0';
do
{
zbx_addr_t *addr;
if (NULL != (r_node = strchr(str, ';')))
*r_node = '\0';
addr = zbx_malloc(NULL, sizeof(zbx_addr_t));
addr->ip = NULL;
addr->revision = 0;
if (SUCCEED != zbx_parse_serveractive_element(str, &addr->ip, &addr->port, port))
{
*error = zbx_dsprintf(NULL, "error parsing the \"%s\" parameter: address \"%s\" is "
"invalid", name, str);
ret = FAIL;
}
else if (FAIL == zbx_is_supported_ip(addr->ip) && FAIL == zbx_validate_hostname(addr->ip))
{
*error = zbx_dsprintf(NULL, "error parsing the \"%s\" parameter: address \"%s\""
" is invalid", name, str);
ret = FAIL;
}
else if (SUCCEED == zbx_vector_addr_ptr_search(&addrs, addr, addr_compare_func))
{
*error = zbx_dsprintf(NULL, "error parsing the \"%s\" parameter: address \"%s\""
" specified more than once", name, str);
ret = FAIL;
}
if (NULL != r_node)
{
*r_node = ';';
str = r_node + 1;
}
zbx_vector_addr_ptr_append(&cluster_addrs, addr);
zbx_vector_addr_ptr_append(&addrs, addr);
if (FAIL == ret)
goto fail;
}
while (NULL != r_node);
cb(&cluster_addrs, hostnames, data);
cluster_addrs.values_num = 0;
if (NULL != r)
{
*r = ',';
str = r + 1;
}
}
while (NULL != r);
fail:
zbx_vector_addr_ptr_destroy(&cluster_addrs);
zbx_vector_addr_ptr_clear_ext(&addrs, zbx_addr_free);
zbx_vector_addr_ptr_destroy(&addrs);
return ret;
}