/* ** 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 <https://www.gnu.org/licenses/>. **/ #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 <shlwapi.h> #else #include <libgen.h> #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; }