/* ** Copyright (C) 2001-2024 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 "zbxsysinfo.h" #include "sysinfo.h" #include "alias/alias.h" #if !defined(WITH_AGENT2_METRICS) # include "zbxthreads.h" #endif #if (!defined(_WINDOWS) && !defined(__MINGW32__)) && !defined(WITH_AGENT2_METRICS) # include "zbxlog.h" # include "zbxnix.h" # include "zbxfile.h" #endif #if defined(_WINDOWS) || defined(__MINGW32__) # include "zbxlog.h" #endif #include "zbxalgo.h" #include "zbxregexp.h" #include "zbxstr.h" #include "zbxnum.h" #include "zbxparam.h" #include "zbxexpr.h" #include "zbxtime.h" #ifdef WITH_AGENT_METRICS # include "agent/agent.h" #endif #ifdef WITH_COMMON_METRICS # include "common/zbxsysinfo_common.h" #endif #ifdef WITH_HTTP_METRICS # include "common/http_metrics.h" #endif #ifdef WITH_SIMPLE_METRICS # include "simple/simple.h" #endif #ifdef WITH_SPECIFIC_METRICS # include "specsysinfo.h" #endif typedef struct { char *pattern; zbx_vector_str_t elements; zbx_key_access_rule_type_t type; int empty_arguments; } zbx_key_access_rule_t; #ifdef WITH_HOSTNAME_METRIC static zbx_metric_t parameter_hostname = /* KEY FLAG FUNCTION TEST PARAMETERS */ #ifdef ZBX_UNKNOWN_ARCH {"system.hostname", 0, system_hostname, NULL}; #else {"system.hostname", CF_HAVEPARAMS, system_hostname, NULL}; #endif #endif static zbx_metric_t *commands = NULL; static zbx_metric_t *commands_local = NULL; static zbx_get_config_int_f get_config_enable_remote_commands_cb = NULL; static zbx_vector_ptr_t key_access_rules; zbx_vector_ptr_t *get_key_access_rules(void) { return &key_access_rules; } #define ZBX_COMMAND_ERROR 0 #define ZBX_COMMAND_WITHOUT_PARAMS 1 #define ZBX_COMMAND_WITH_PARAMS 2 #define GET_CONFIG_VAR(callback_type, callback, func_type, func) \ static callback_type callback = NULL; \ func_type func(void) \ { \ return callback(); \ } GET_CONFIG_VAR(zbx_get_config_int_f, get_config_timeout_cb, int, sysinfo_get_config_timeout) GET_CONFIG_VAR(zbx_get_config_int_f, get_config_log_remote_commands_cb, int, sysinfo_get_config_log_remote_commands) GET_CONFIG_VAR(zbx_get_config_int_f, get_config_unsafe_user_parameters_cb, int, sysinfo_get_config_unsafe_user_parameters) GET_CONFIG_VAR(zbx_get_config_str_f, get_config_source_ip_cb, const char *, sysinfo_get_config_source_ip) GET_CONFIG_VAR(zbx_get_config_str_f, get_config_hostname_cb, const char *, sysinfo_get_config_hostname) GET_CONFIG_VAR(zbx_get_config_str_f, get_config_hostnames_cb, const char *, sysinfo_get_config_hostnames) GET_CONFIG_VAR(zbx_get_config_str_f, get_config_host_metadata_cb, const char *, sysinfo_get_config_host_metadata) GET_CONFIG_VAR(zbx_get_config_str_f, get_config_host_metadata_item_cb, const char *, sysinfo_get_config_host_metadata_item) GET_CONFIG_VAR(zbx_get_config_str_f, get_config_service_name_cb, const char *, sysinfo_get_config_service_name) #undef GET_CONFIG_VAR static int compare_key_access_rules(const void *rule_a, const void *rule_b); static int zbx_parse_key_access_rule(char *pattern, zbx_key_access_rule_t *rule); /****************************************************************************** * * * Purpose: parses item key and splits it into command and parameters * * * * Return value: ZBX_COMMAND_ERROR - error * * ZBX_COMMAND_WITHOUT_PARAMS - command without parameters * * ZBX_COMMAND_WITH_PARAMS - command with parameters * * * ******************************************************************************/ static int parse_command_dyn(const char *command, char **cmd, char **param) { const char *pl, *pr; size_t cmd_alloc = 0, param_alloc = 0, cmd_offset = 0, param_offset = 0; for (pl = command; SUCCEED == zbx_is_key_char(*pl); pl++) ; if (pl == command) return ZBX_COMMAND_ERROR; zbx_strncpy_alloc(cmd, &cmd_alloc, &cmd_offset, command, pl - command); if ('\0' == *pl) /* no parameters specified */ return ZBX_COMMAND_WITHOUT_PARAMS; if ('[' != *pl) /* unsupported character */ return ZBX_COMMAND_ERROR; for (pr = ++pl; '\0' != *pr; pr++) ; if (']' != *--pr) return ZBX_COMMAND_ERROR; zbx_strncpy_alloc(param, ¶m_alloc, ¶m_offset, pl, pr - pl); return ZBX_COMMAND_WITH_PARAMS; } static int add_to_metrics(zbx_metric_t **metrics, zbx_metric_t *metric, char *error, size_t max_error_len) { int i = 0; while (NULL != (*metrics)[i].key) { if (0 == strcmp((*metrics)[i].key, metric->key)) { zbx_snprintf(error, max_error_len, "key \"%s\" already exists", metric->key); return FAIL; /* metric already exists */ } i++; } (*metrics)[i].key = zbx_strdup(NULL, metric->key); (*metrics)[i].flags = metric->flags; (*metrics)[i].function = metric->function; (*metrics)[i].test_param = (NULL == metric->test_param ? NULL : zbx_strdup(NULL, metric->test_param)); *metrics = (zbx_metric_t *)zbx_realloc(*metrics, (i + 2) * sizeof(zbx_metric_t)); memset(&(*metrics)[i + 1], 0, sizeof(zbx_metric_t)); return SUCCEED; } void zbx_init_library_sysinfo(zbx_get_config_int_f get_config_timeout_f, zbx_get_config_int_f get_config_enable_remote_commands_f, zbx_get_config_int_f get_config_log_remote_commands_f, zbx_get_config_int_f get_config_unsafe_user_parameters_f, zbx_get_config_str_f get_config_source_ip_f, zbx_get_config_str_f get_config_hostname_f, zbx_get_config_str_f get_config_hostnames_f, zbx_get_config_str_f get_config_host_metadata_f, zbx_get_config_str_f get_config_host_metadata_item_f, zbx_get_config_str_f get_config_service_name_f) { get_config_timeout_cb = get_config_timeout_f; get_config_enable_remote_commands_cb = get_config_enable_remote_commands_f; get_config_log_remote_commands_cb = get_config_log_remote_commands_f; get_config_unsafe_user_parameters_cb = get_config_unsafe_user_parameters_f; get_config_source_ip_cb = get_config_source_ip_f; get_config_hostname_cb = get_config_hostname_f; get_config_hostnames_cb = get_config_hostnames_f; get_config_host_metadata_cb = get_config_host_metadata_f; get_config_host_metadata_item_cb = get_config_host_metadata_item_f; get_config_service_name_cb = get_config_service_name_f; } /****************************************************************************** * * * Purpose: registers new item key into system * * * ******************************************************************************/ int zbx_add_metric(zbx_metric_t *metric, char *error, size_t max_error_len) { return add_to_metrics(&commands, metric, error, max_error_len); } #ifdef WITH_COMMON_METRICS /****************************************************************************** * * * Purpose: registers new item key as local into system * * * ******************************************************************************/ static int add_metric_local(zbx_metric_t *metric, char *error, size_t max_error_len) { return add_to_metrics(&commands_local, metric, error, max_error_len); } #endif /* WITH_COMMON_METRICS */ #if !defined(__MINGW32__) int zbx_add_user_parameter(const char *itemkey, char *command, char *error, size_t max_error_len) { int ret; unsigned flags = CF_USERPARAMETER; zbx_metric_t metric; AGENT_REQUEST request; zbx_init_agent_request(&request); if (SUCCEED == (ret = zbx_parse_item_key(itemkey, &request))) { if (1 == get_rparams_num(&request) && 0 == strcmp("[*]", itemkey + strlen(get_rkey(&request)))) flags |= CF_HAVEPARAMS; else if (0 != get_rparams_num(&request)) ret = FAIL; } if (SUCCEED == ret) { metric.key = get_rkey(&request); metric.flags = flags; metric.function = &execute_user_parameter; metric.test_param = command; ret = zbx_add_metric(&metric, error, max_error_len); } else zbx_strlcpy(error, "syntax error", max_error_len); zbx_free_agent_request(&request); return ret; } void zbx_remove_user_parameters(void) { int usr = -1; if (NULL == commands) return; for (int i = 0; NULL != commands[i].key; i++) { if (0 != (CF_USERPARAMETER & commands[i].flags)) { zbx_free(commands[i].key); zbx_free(commands[i].test_param); if (0 > usr) usr = i; } } if (0 < usr) { commands = (zbx_metric_t *)zbx_realloc(commands, ((unsigned int)usr + 1) * sizeof(zbx_metric_t)); memset(&commands[usr], 0, sizeof(zbx_metric_t)); } else if (0 == usr) { zbx_free(commands); } } void zbx_get_metrics_copy(zbx_metric_t **metrics) { unsigned int i; if (NULL == commands) { *metrics = NULL; return; } for (i = 0; NULL != commands[i].key; i++) ; *metrics = (zbx_metric_t *)zbx_malloc(*metrics, sizeof(zbx_metric_t) * (i + 1)); for (i = 0; NULL != commands[i].key; i++) { (*metrics)[i].key = zbx_strdup(NULL, commands[i].key); (*metrics)[i].flags = commands[i].flags; (*metrics)[i].function = commands[i].function; (*metrics)[i].test_param = (NULL == commands[i].test_param ? NULL : zbx_strdup(NULL, commands[i].test_param)); } memset(&(*metrics)[i], 0, sizeof(zbx_metric_t)); } void zbx_set_metrics(zbx_metric_t *metrics) { zbx_free_metrics_ext(&commands); commands = metrics; } #endif void zbx_init_metrics(void) { #if (defined(WITH_AGENT_METRICS) || defined(WITH_COMMON_METRICS) || defined(WITH_HTTP_METRICS) || \ defined(WITH_SPECIFIC_METRICS) || defined(WITH_SIMPLE_METRICS)) char error[MAX_STRING_LEN]; #elif (defined(WITH_HOSTNAME_METRIC)) char error[MAX_STRING_LEN]; #endif zbx_init_key_access_rules(); commands = (zbx_metric_t *)zbx_malloc(commands, sizeof(zbx_metric_t)); commands[0].key = NULL; commands_local = (zbx_metric_t *)zbx_malloc(commands_local, sizeof(zbx_metric_t)); commands_local[0].key = NULL; #ifdef WITH_AGENT_METRICS zbx_metric_t *parameters_agent = get_parameters_agent(); for (int i = 0; NULL != parameters_agent[i].key; i++) { if (SUCCEED != zbx_add_metric(¶meters_agent[i], error, sizeof(error))) { zabbix_log(LOG_LEVEL_CRIT, "cannot add item key: %s", error); exit(EXIT_FAILURE); } } #endif #ifdef WITH_COMMON_METRICS zbx_metric_t *parameters_common = get_parameters_common(); for (int i = 0; NULL != parameters_common[i].key; i++) { if (SUCCEED != zbx_add_metric(¶meters_common[i], error, sizeof(error))) { zabbix_log(LOG_LEVEL_CRIT, "cannot add item key: %s", error); exit(EXIT_FAILURE); } } zbx_metric_t *parameters_common_local = get_parameters_common_local(); for (int i = 0; NULL != parameters_common_local[i].key; i++) { if (SUCCEED != add_metric_local(¶meters_common_local[i], error, sizeof(error))) { zabbix_log(LOG_LEVEL_CRIT, "cannot add item key: %s", error); exit(EXIT_FAILURE); } } #endif #ifdef WITH_HTTP_METRICS zbx_metric_t *parameters_common_http = get_parameters_common_http(); for (int i = 0; NULL != parameters_common_http[i].key; i++) { if (SUCCEED != zbx_add_metric(¶meters_common_http[i], error, sizeof(error))) { zabbix_log(LOG_LEVEL_CRIT, "cannot add item key: %s", error); exit(EXIT_FAILURE); } } #endif #ifdef WITH_SPECIFIC_METRICS zbx_metric_t *parameters_specific = get_parameters_specific(); for (int i = 0; NULL != parameters_specific[i].key; i++) { if (SUCCEED != zbx_add_metric(¶meters_specific[i], error, sizeof(error))) { zabbix_log(LOG_LEVEL_CRIT, "cannot add item key: %s", error); exit(EXIT_FAILURE); } } #endif #ifdef WITH_SIMPLE_METRICS zbx_metric_t *parameters_simple = get_parameters_simple(); for (int i = 0; NULL != parameters_simple[i].key; i++) { if (SUCCEED != zbx_add_metric(¶meters_simple[i], error, sizeof(error))) { zabbix_log(LOG_LEVEL_CRIT, "cannot add item key: %s", error); exit(EXIT_FAILURE); } } #endif #ifdef WITH_HOSTNAME_METRIC if (SUCCEED != zbx_add_metric(¶meter_hostname, error, sizeof(error))) { zabbix_log(LOG_LEVEL_CRIT, "cannot add item key: %s", error); exit(EXIT_FAILURE); } #endif } void zbx_free_metrics_ext(zbx_metric_t **metrics) { if (NULL != *metrics) { for (int i = 0; NULL != (*metrics)[i].key; i++) { zbx_free((*metrics)[i].key); zbx_free((*metrics)[i].test_param); } zbx_free(*metrics); } } void zbx_free_metrics(void) { zbx_free_metrics_ext(&commands); zbx_free_metrics_ext(&commands_local); zbx_free_key_access_rules(); } /****************************************************************************** * * * Purpose: initializes key access rule list * * * ******************************************************************************/ void zbx_init_key_access_rules(void) { zbx_vector_ptr_create(&key_access_rules); } /****************************************************************************** * * * Purpose: frees key access rule and its resources * * * ******************************************************************************/ static void zbx_key_access_rule_free(zbx_key_access_rule_t *rule) { zbx_free(rule->pattern); zbx_vector_str_clear_ext(&rule->elements, zbx_str_free); zbx_vector_str_destroy(&rule->elements); zbx_free(rule); } /****************************************************************************** * * * Purpose: creates key access rule * * * * Parameters: pattern - [IN] rule pattern * * type - [IN] rule type * * * * Return value: created rule or NULL if pattern was invalid * * * ******************************************************************************/ static zbx_key_access_rule_t *zbx_key_access_rule_create(char *pattern, zbx_key_access_rule_type_t type) { zbx_key_access_rule_t *rule; rule = zbx_malloc(NULL, sizeof(zbx_key_access_rule_t)); rule->type = type; rule->pattern = zbx_strdup(NULL, pattern); zbx_vector_str_create(&rule->elements); if (SUCCEED != zbx_parse_key_access_rule(pattern, rule)) { zbx_key_access_rule_free(rule); rule = NULL; } return rule; } /****************************************************************************** * * * Purpose: validates key access rules configuration * * * ******************************************************************************/ void zbx_finalize_key_access_rules_configuration(void) { int i, j, rules_num, sysrun_index = ZBX_MAX_UINT31_1; zbx_key_access_rule_t *rule, *sysrun_deny; char sysrun_pattern[] = "system.run[*]"; rules_num = key_access_rules.values_num; /* prepare default system.run[*] deny rule to be added at the end */ if (NULL == (sysrun_deny = zbx_key_access_rule_create(sysrun_pattern, ZBX_KEY_ACCESS_DENY))) { THIS_SHOULD_NEVER_HAPPEN; exit(EXIT_FAILURE); } if (FAIL != (i = zbx_vector_ptr_search(&key_access_rules, sysrun_deny, compare_key_access_rules))) { /* exclude system.run[*] from total number of rules */ rules_num--; /* sysrun_index points at the first rule matching system.run[*] */ sysrun_index = i; } if (0 != rules_num) { /* throw out all rules after '*', because they would never match */ for (i = 0; i < key_access_rules.values_num; i++) { rule = (zbx_key_access_rule_t*)key_access_rules.values[i]; if (1 == rule->elements.values_num && 0 == strcmp(rule->elements.values[0], "*")) { /* 'match all' rule also matches system.run[*] */ if (i < sysrun_index) sysrun_index = i; break; } } if (i != key_access_rules.values_num) { for (j = ++i; j < key_access_rules.values_num; j++) { rule = (zbx_key_access_rule_t*)key_access_rules.values[j]; zabbix_log(LOG_LEVEL_WARNING, "removed unreachable %s \"%s\" rule", (ZBX_KEY_ACCESS_ALLOW == rule->type ? "AllowKey" : "DenyKey"), rule->pattern); zbx_key_access_rule_free(rule); } key_access_rules.values_num = i; } /* trailing AllowKey rules are meaningless, because AllowKey=* is default behavior, */ for (i = key_access_rules.values_num - 1; 0 <= i; i--) { rule = (zbx_key_access_rule_t*)key_access_rules.values[i]; if (ZBX_KEY_ACCESS_ALLOW != rule->type) break; /* system.run allow rules are not redundant because of default system.run[*] deny rule */ if (0 == rule->elements.values_num || 0 != strcmp(rule->elements.values[0], "system.run")) { if (i != sysrun_index) { zabbix_log(LOG_LEVEL_WARNING, "removed redundant trailing AllowKey \"%s\" rule", rule->pattern); } zbx_key_access_rule_free(rule); zbx_vector_ptr_remove(&key_access_rules, i); } } if (0 == key_access_rules.values_num) { zabbix_log(LOG_LEVEL_CRIT, "Item key access rules are configured to match all keys," " indicating possible configuration problem. " " Please remove the rules if that was the purpose."); exit(EXIT_FAILURE); } } if (ZBX_MAX_UINT31_1 == sysrun_index) zbx_vector_ptr_append(&key_access_rules, sysrun_deny); else zbx_key_access_rule_free(sysrun_deny); } /****************************************************************************** * * * Purpose: parses key access rule expression from AllowKey and DenyKey * * * * Parameters: pattern - [IN] key access rule wildcard * * rule - [IN] key access rule element to fill * * * * Return value: SUCCEED - successful execution * * FAIL - pattern parsing failed * * * ******************************************************************************/ static int zbx_parse_key_access_rule(char *pattern, zbx_key_access_rule_t *rule) { char *pl, *pr = NULL, *param; size_t alloc = 0, offset = 0; int i, size; for (pl = pattern; SUCCEED == zbx_is_key_char(*pl) || '*' == *pl; pl++); if (pl == pattern) return FAIL; /* empty key */ /* extract rule elements [0] = key pattern and all parameters follow */ zbx_strncpy_alloc(&pr, &alloc, &offset, pattern, pl - pattern); zbx_wildcard_minimize(pr); zbx_vector_str_append(&rule->elements, pr); rule->empty_arguments = 0; if ('\0' == *pl) /* no parameters specified */ return SUCCEED; if ('[' != *pl) /* unsupported character */ return FAIL; for (pr = ++pl; '\0' != *pr; pr++); if (']' != *--pr) return FAIL; if (1 > pr - pl) /* no parameters specified */ { rule->empty_arguments = 1; return SUCCEED; } *pr = '\0'; size = zbx_num_param(pl); zbx_vector_str_reserve(&rule->elements, size); for (i = 0; i < size; i++) { if (NULL == (param = zbx_get_param_dyn(pl, i + 1, NULL))) return FAIL; zbx_wildcard_minimize(param); zbx_vector_str_append(&rule->elements, param); } *pr = ']'; /* remove repeated trailing "*" parameters */ if (1 < size && 0 == strcmp(rule->elements.values[i--], "*")) { for (; 0 < i; i--) { if (0 != strcmp(rule->elements.values[i], "*")) break; zbx_free(rule->elements.values[i + 1]); zbx_vector_str_remove(&rule->elements, i + 1); } } return SUCCEED; } /****************************************************************************** * * * Purpose: Compares two zbx_key_access_rule_t values to perform search * * within vector. Rule type (allow/deny) is not checked here. * * * * Parameters: rule_a - [IN] key access rule 1 * * rule_b - [IN] key access rule 2 * * * * Return value: If key access rule values are the same, 0 is returned * * otherwise nonzero value is returned. * * * ******************************************************************************/ static int compare_key_access_rules(const void *rule_a, const void *rule_b) { const zbx_key_access_rule_t *a, *b; a = *(zbx_key_access_rule_t * const *)rule_a; b = *(zbx_key_access_rule_t * const *)rule_b; if (a->empty_arguments != b->empty_arguments || a->elements.values_num != b->elements.values_num) return 1; for (int i = 0; a->elements.values_num > i; i++) { if (0 != strcmp(a->elements.values[i], b->elements.values[i])) return 1; } return 0; } /****************************************************************************** * * * Purpose: adds new key access rule from AllowKey and DenyKey parameters * * * * Parameters: parameter - [IN] parameter that defined rule * * pattern - [IN] key access rule wildcard * * type - [IN] key access rule type (allow/deny) * * * * Return value: SUCCEED - successful execution * * FAIL - pattern parsing failed * * * ******************************************************************************/ int zbx_add_key_access_rule(const char *parameter, char *pattern, zbx_key_access_rule_type_t type) { zbx_key_access_rule_t *rule, *r; int i; if (NULL == (rule = zbx_key_access_rule_create(pattern, type))) { zabbix_log(LOG_LEVEL_WARNING, "failed to process %s access rule \"%s\"", parameter, pattern); return FAIL; } if (FAIL != (i = zbx_vector_ptr_search(&key_access_rules, rule, compare_key_access_rules))) { r = (zbx_key_access_rule_t*)key_access_rules.values[i]; zabbix_log(LOG_LEVEL_WARNING, "%s access rule \"%s\" was not added" " because it %s another rule defined above ", parameter, pattern, r->type == type ? "duplicates" : "conflicts with"); zbx_key_access_rule_free(rule); return SUCCEED; } zbx_vector_ptr_append(&key_access_rules, rule); return SUCCEED; } /****************************************************************************** * * * Purpose: checks agent metric request against configured access rules * * * * Parameters: request - [IN] metric request (key and parameters) * * * * Return value: ZBX_KEY_ACCESS_ALLOW - metric access allowed * * ZBX_KEY_ACCESS_DENY - metric access denied * * * ******************************************************************************/ int zbx_check_request_access_rules(AGENT_REQUEST *request) { zbx_key_access_rule_t *rule; /* empty arguments flag means key is followed by empty brackets, which is not the same as no brackets */ int empty_arguments = (1 == request->nparam && 0 == strlen(request->params[0])); for (int i = 0; key_access_rules.values_num > i; i++) { rule = (zbx_key_access_rule_t*)key_access_rules.values[i]; if (0 == strcmp("*", rule->elements.values[0]) && 1 == rule->elements.values_num) return rule->type; /* match all */ if (1 < rule->elements.values_num) { if (0 == strcmp("*", rule->elements.values[rule->elements.values_num - 1])) { if (2 == rule->elements.values_num && 0 == request->nparam) continue; /* rule: key[*], request: key */ } else { if (request->nparam < (rule->elements.values_num - 1)) continue; /* too few parameters */ if (request->nparam > (rule->elements.values_num - 1) && 0 == empty_arguments) continue; /* too many parameters */ } } if (0 == zbx_wildcard_match(request->key, rule->elements.values[0])) continue; /* key doesn't match */ if (0 != rule->empty_arguments) /* rule expects empty argument list: key[] */ { if (0 != empty_arguments) return rule->type; continue; } if (0 != empty_arguments && 1 == rule->elements.values_num) continue; /* no parameters expected by rule */ if (0 == request->nparam && 1 == rule->elements.values_num) /* no parameters */ return rule->type; for (int j = 1; rule->elements.values_num > j; j++) { if ((rule->elements.values_num - 1) == j) /* last parameter */ { if (0 == strcmp("*", rule->elements.values[j])) return rule->type; /* skip next parameter checks */ if (request->nparam < j) break; if (0 == zbx_wildcard_match(request->params[j - 1], rule->elements.values[j])) break; /* parameter doesn't match pattern */ return rule->type; } if (request->nparam < j || 0 == zbx_wildcard_match(request->params[j - 1], rule->elements.values[j])) break; /* parameter doesn't match pattern */ } } return ZBX_KEY_ACCESS_ALLOW; /* allow by default for backward compatibility */ } /****************************************************************************** * * * Purpose: checks agent metric request against configured access rules * * * * Parameters: metric - [IN] metric requested (key and parameters) * * * * Return value: ZBX_KEY_ACCESS_ALLOW - metric access allowed * * ZBX_KEY_ACCESS_DENY - metric access denied * * * ******************************************************************************/ int zbx_check_key_access_rules(const char *metric) { int ret; AGENT_REQUEST request; zbx_init_agent_request(&request); if (SUCCEED == zbx_parse_item_key(metric, &request)) ret = zbx_check_request_access_rules(&request); else ret = ZBX_KEY_ACCESS_DENY; zbx_free_agent_request(&request); return ret; } /****************************************************************************** * * * Purpose: cleans-up key access rule list * * * ******************************************************************************/ void zbx_free_key_access_rules(void) { for (int i = 0; i < key_access_rules.values_num; i++) zbx_key_access_rule_free((zbx_key_access_rule_t *)key_access_rules.values[i]); zbx_vector_ptr_destroy(&key_access_rules); } static void zbx_log_init(zbx_log_t *log) { log->value = NULL; log->source = NULL; log->timestamp = 0; log->severity = 0; log->logeventid = 0; } /****************************************************************************** * * * Purpose: initializes request structure * * * * Parameters: request - pointer to structure * * * ******************************************************************************/ void zbx_init_agent_request(AGENT_REQUEST *request) { request->key = NULL; request->nparam = 0; request->params = NULL; request->types = NULL; request->lastlogsize = 0; request->mtime = 0; } /****************************************************************************** * * * Purpose: frees memory used by request parameters * * * * Parameters: request - pointer to request structure * * * ******************************************************************************/ static void free_request_params(AGENT_REQUEST *request) { for (int i = 0; i < request->nparam; i++) zbx_free(request->params[i]); zbx_free(request->params); zbx_free(request->types); request->nparam = 0; } /****************************************************************************** * * * Purpose: frees memory used by request * * * * Parameters: request - pointer to request structure * * * ******************************************************************************/ void zbx_free_agent_request(AGENT_REQUEST *request) { zbx_free(request->key); free_request_params(request); } /****************************************************************************** * * * Purpose: adds new parameter * * * * Parameters: request - [OUT] pointer to request structure * * pvalue - [IN] parameter value string * * type - [IN] parameter type * * * ******************************************************************************/ static void add_request_param(AGENT_REQUEST *request, char *pvalue, zbx_request_parameter_type_t type) { request->nparam++; request->params = (char **)zbx_realloc(request->params, request->nparam * sizeof(char *)); request->params[request->nparam - 1] = pvalue; request->types = (zbx_request_parameter_type_t*)zbx_realloc(request->types, request->nparam * sizeof(zbx_request_parameter_type_t)); request->types[request->nparam - 1] = type; } /****************************************************************************** * * * Purpose: parses item command (key) and fill AGENT_REQUEST structure * * * * Parameters: itemkey - [IN] complete item key * * request - [OUT] structure filled with data from item key * * * * Return value: SUCCEED - key was parsed successfully * * FAIL - otherwise * * * * Comments: thread-safe * * * ******************************************************************************/ int zbx_parse_item_key(const char *itemkey, AGENT_REQUEST *request) { int ret = FAIL; char *key = NULL, *params = NULL; switch (parse_command_dyn(itemkey, &key, ¶ms)) { case ZBX_COMMAND_WITH_PARAMS: if (0 == (request->nparam = zbx_num_param(params))) goto out; /* key is badly formatted */ request->params = (char **)zbx_malloc(request->params, request->nparam * sizeof(char *)); request->types = (zbx_request_parameter_type_t*)zbx_malloc(request->types, request->nparam * sizeof(zbx_request_parameter_type_t)); for (int i = 0; i < request->nparam; i++) request->params[i] = zbx_get_param_dyn(params, i + 1, &request->types[i]); break; case ZBX_COMMAND_ERROR: goto out; /* key is badly formatted */ } request->key = key; key = NULL; ret = SUCCEED; out: zbx_free(params); zbx_free(key); return ret; } #undef ZBX_COMMAND_ERROR #undef ZBX_COMMAND_WITHOUT_PARAMS #undef ZBX_COMMAND_WITH_PARAMS void zbx_test_parameter(const char *key) { #define ZBX_KEY_COLUMN_WIDTH 45 AGENT_RESULT result; printf("%-*s", ZBX_KEY_COLUMN_WIDTH, key); zbx_init_agent_result(&result); if (SUCCEED == zbx_execute_agent_check(key, ZBX_PROCESS_WITH_ALIAS, &result, ZBX_CHECK_TIMEOUT_UNDEFINED)) { char buffer[ZBX_MAX_DOUBLE_LEN + 1]; if (0 == ZBX_ISSET_VALUE(&result)) printf(" [-|" ZBX_NODATA "]"); if (0 != ZBX_ISSET_UI64(&result)) printf(" [u|" ZBX_FS_UI64 "]", result.ui64); if (0 != ZBX_ISSET_DBL(&result)) printf(" [d|%s]", zbx_print_double(buffer, sizeof(buffer), result.dbl)); if (0 != ZBX_ISSET_STR(&result)) printf(" [s|%s]", result.str); if (0 != ZBX_ISSET_TEXT(&result)) printf(" [t|%s]", result.text); if (0 != ZBX_ISSET_MSG(&result)) printf(" [m|%s]", result.msg); } else { if (0 != ZBX_ISSET_MSG(&result)) printf(" [m|" ZBX_NOTSUPPORTED "] [%s]", result.msg); else printf(" [m|" ZBX_NOTSUPPORTED "]"); } zbx_free_agent_result(&result); printf("\n"); fflush(stdout); #undef ZBX_KEY_COLUMN_WIDTH } void zbx_test_parameters(void) { char *key = NULL; size_t key_alloc = 0; for (int i = 0; NULL != commands[i].key; i++) { if (0 != strcmp(commands[i].key, "__UserPerfCounter")) { size_t key_offset = 0; zbx_strcpy_alloc(&key, &key_alloc, &key_offset, commands[i].key); if (0 == (commands[i].flags & CF_USERPARAMETER) && NULL != commands[i].test_param) { zbx_chrcpy_alloc(&key, &key_alloc, &key_offset, '['); zbx_strcpy_alloc(&key, &key_alloc, &key_offset, commands[i].test_param); zbx_chrcpy_alloc(&key, &key_alloc, &key_offset, ']'); } if (ZBX_KEY_ACCESS_ALLOW == zbx_check_key_access_rules(key)) zbx_test_parameter(key); } } zbx_free(key); test_aliases(); } static int zbx_check_user_parameter(const char *param, int config_unsafe_user_parameters, char *error, int max_error_len) { const char suppressed_chars[] = "\\'\"`*?[]{}~$!&;()<>|#@\n", *c; char *buf = NULL; size_t buf_alloc = 128, buf_offset = 0; if (0 != config_unsafe_user_parameters) return SUCCEED; for (c = suppressed_chars; '\0' != *c; c++) { if (NULL == strchr(param, *c)) continue; buf = (char *)zbx_malloc(buf, buf_alloc); for (c = suppressed_chars; '\0' != *c; c++) { if (c != suppressed_chars) zbx_strcpy_alloc(&buf, &buf_alloc, &buf_offset, ", "); if (0 != isprint(*c)) zbx_chrcpy_alloc(&buf, &buf_alloc, &buf_offset, *c); else zbx_snprintf_alloc(&buf, &buf_alloc, &buf_offset, "0x%02x", (unsigned int)(*c)); } zbx_snprintf(error, max_error_len, "Special characters \"%s\" are not allowed in the parameters.", buf); zbx_free(buf); return FAIL; } return SUCCEED; } static int replace_param(const char *cmd, const AGENT_REQUEST *request, int config_user_parameters, char **out, char *error, int max_error_len) { const char *pl = cmd, *pr, *tmp; size_t out_alloc = 0, out_offset = 0; int num, ret = SUCCEED; while (NULL != (pr = strchr(pl, '$'))) { zbx_strncpy_alloc(out, &out_alloc, &out_offset, pl, pr - pl); /* check if increasing pointer by 1 will not result in buffer overrun */ if ('\0' != pr[1]) pr++; if ('0' == *pr) { zbx_strcpy_alloc(out, &out_alloc, &out_offset, cmd); } else if ('1' <= *pr && *pr <= '9') { num = (int)(*pr - '0'); if (request->nparam >= num) { tmp = get_rparam(request, num - 1); if (SUCCEED != (ret = zbx_check_user_parameter(tmp, config_user_parameters, error, max_error_len))) { break; } zbx_strcpy_alloc(out, &out_alloc, &out_offset, tmp); } } else { if ('$' != *pr) zbx_chrcpy_alloc(out, &out_alloc, &out_offset, '$'); zbx_chrcpy_alloc(out, &out_alloc, &out_offset, *pr); } pl = pr + 1; } if (SUCCEED == ret) zbx_strcpy_alloc(out, &out_alloc, &out_offset, pl); else zbx_free(*out); return ret; } /********************************************************************************** * * * Parameters: in_command - [IN] item key * * flags - [IN] * * ZBX_PROCESS_LOCAL_COMMAND, allow execution of system.run * * ZBX_PROCESS_MODULE_COMMAND, execute item from a module * * ZBX_PROCESS_WITH_ALIAS, substitute agent Alias * * timeout - [IN] check execution timeout * * result - [OUT] * * * * Return value: SUCCEED - successful execution * * NOTSUPPORTED - item key is not supported or other error * * result - contains item value or error message * * * **********************************************************************************/ int zbx_execute_agent_check(const char *in_command, unsigned flags, AGENT_RESULT *result, int timeout) { int ret = NOTSUPPORTED; zbx_metric_t *command = NULL; AGENT_REQUEST request; zbx_init_agent_request(&request); if (SUCCEED != zbx_parse_item_key((0 == (flags & ZBX_PROCESS_WITH_ALIAS) ? in_command : zbx_alias_get(in_command)), &request)) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid item key format.")); goto notsupported; } if (0 == (flags & ZBX_PROCESS_LOCAL_COMMAND) && ZBX_KEY_ACCESS_ALLOW != zbx_check_request_access_rules(&request)) { zabbix_log(LOG_LEVEL_DEBUG, "Key access denied: \"%s\"", in_command); SET_MSG_RESULT(result, zbx_strdup(NULL, "Unsupported item key.")); goto notsupported; } /* system.run is not allowed by default except for getting hostname for daemons */ if (1 != get_config_enable_remote_commands_cb() && 0 == (flags & ZBX_PROCESS_LOCAL_COMMAND) && 0 == strcmp(request.key, "system.run")) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Remote commands are not enabled.")); goto notsupported; } if (0 != (flags & ZBX_PROCESS_LOCAL_COMMAND)) { for (command = commands_local; NULL != command->key; command++) { if (0 == strcmp(command->key, request.key)) break; } } if (NULL == command || NULL == command->key) { for (command = commands; NULL != command->key; command++) { if (0 == strcmp(command->key, request.key)) break; } } /* item key not found */ if (NULL == command->key) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Unsupported item key.")); goto notsupported; } /* expected item from a module */ if (0 != (flags & ZBX_PROCESS_MODULE_COMMAND) && 0 == (command->flags & CF_MODULE)) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Unsupported item key.")); goto notsupported; } /* command does not accept parameters but was called with parameters */ if (0 == (command->flags & CF_HAVEPARAMS) && 0 != request.nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Item does not allow parameters.")); goto notsupported; } if (0 != (command->flags & CF_USERPARAMETER)) { if (0 != (command->flags & CF_HAVEPARAMS)) { char *parameters = NULL, error[MAX_STRING_LEN]; if (FAIL == replace_param(command->test_param, &request, get_config_unsafe_user_parameters_cb(), ¶meters, error, sizeof(error))) { SET_MSG_RESULT(result, zbx_strdup(NULL, error)); goto notsupported; } free_request_params(&request); add_request_param(&request, parameters, REQUEST_PARAMETER_TYPE_STRING); } else { free_request_params(&request); add_request_param(&request, zbx_strdup(NULL, command->test_param), REQUEST_PARAMETER_TYPE_STRING); } } request.timeout = (0 == timeout ? sysinfo_get_config_timeout() : timeout); if (SYSINFO_RET_OK != command->function(&request, result)) { /* "return NOTSUPPORTED;" would be more appropriate here for preserving original error */ /* message in "result" but would break things relying on ZBX_NOTSUPPORTED message. */ if (0 != (command->flags & CF_MODULE) && 0 == ZBX_ISSET_MSG(result)) SET_MSG_RESULT(result, zbx_strdup(NULL, ZBX_NOTSUPPORTED_MSG)); goto notsupported; } ret = SUCCEED; notsupported: zbx_free_agent_request(&request); return ret; } static void add_log_result(AGENT_RESULT *result, const char *value) { result->log = (zbx_log_t *)zbx_malloc(result->log, sizeof(zbx_log_t)); zbx_log_init(result->log); result->log->value = zbx_strdup(result->log->value, value); result->type |= AR_LOG; } int zbx_set_agent_result_type(AGENT_RESULT *result, int value_type, char *c) { zbx_uint64_t value_uint64; int ret = FAIL; double dbl_tmp; assert(result); switch (value_type) { case ITEM_VALUE_TYPE_UINT64: zbx_trim_integer(c); zbx_del_zeros(c); if (SUCCEED == zbx_is_uint64(c, &value_uint64)) { SET_UI64_RESULT(result, value_uint64); ret = SUCCEED; } break; case ITEM_VALUE_TYPE_FLOAT: zbx_trim_float(c); if (SUCCEED == zbx_is_double(c, &dbl_tmp)) { SET_DBL_RESULT(result, dbl_tmp); ret = SUCCEED; } break; case ITEM_VALUE_TYPE_STR: zbx_replace_invalid_utf8(c); SET_STR_RESULT(result, zbx_strdup(NULL, c)); ret = SUCCEED; break; case ITEM_VALUE_TYPE_TEXT: zbx_replace_invalid_utf8(c); SET_TEXT_RESULT(result, zbx_strdup(NULL, c)); ret = SUCCEED; break; case ITEM_VALUE_TYPE_LOG: zbx_replace_invalid_utf8(c); add_log_result(result, c); ret = SUCCEED; break; case ITEM_VALUE_TYPE_BIN: case ITEM_VALUE_TYPE_NONE: default: THIS_SHOULD_NEVER_HAPPEN; exit(EXIT_FAILURE); } return ret; } void zbx_set_agent_result_meta(AGENT_RESULT *result, zbx_uint64_t lastlogsize, int mtime) { result->lastlogsize = lastlogsize; result->mtime = mtime; result->type |= AR_META; } static zbx_uint64_t *get_result_ui64_value(AGENT_RESULT *result) { zbx_uint64_t value; assert(result); if (0 != ZBX_ISSET_UI64(result)) { /* nothing to do */ } else if (0 != ZBX_ISSET_DBL(result)) { SET_UI64_RESULT(result, result->dbl); } else if (0 != ZBX_ISSET_STR(result)) { zbx_trim_integer(result->str); zbx_del_zeros(result->str); if (SUCCEED != zbx_is_uint64(result->str, &value)) return NULL; SET_UI64_RESULT(result, value); } else if (0 != ZBX_ISSET_TEXT(result)) { zbx_trim_integer(result->text); zbx_del_zeros(result->text); if (SUCCEED != zbx_is_uint64(result->text, &value)) return NULL; SET_UI64_RESULT(result, value); } /* skip AR_MESSAGE - it is information field */ if (0 != ZBX_ISSET_UI64(result)) return &result->ui64; return NULL; } static double *get_result_dbl_value(AGENT_RESULT *result) { double value; assert(result); if (0 != ZBX_ISSET_DBL(result)) { /* nothing to do */ } else if (0 != ZBX_ISSET_UI64(result)) { SET_DBL_RESULT(result, result->ui64); } else if (0 != ZBX_ISSET_STR(result)) { zbx_trim_float(result->str); if (SUCCEED != zbx_is_double(result->str, &value)) return NULL; SET_DBL_RESULT(result, value); } else if (0 != ZBX_ISSET_TEXT(result)) { zbx_trim_float(result->text); if (SUCCEED != zbx_is_double(result->text, &value)) return NULL; SET_DBL_RESULT(result, value); } /* skip AR_MESSAGE - it is information field */ if (0 != ZBX_ISSET_DBL(result)) return &result->dbl; return NULL; } static char **get_result_str_value(AGENT_RESULT *result) { char *p, tmp; assert(result); if (0 != ZBX_ISSET_STR(result)) { /* nothing to do */ } else if (0 != ZBX_ISSET_TEXT(result)) { /* NOTE: copy only line */ for (p = result->text; '\0' != *p && '\r' != *p && '\n' != *p; p++); tmp = *p; /* remember result->text character */ *p = '\0'; /* temporary replace */ SET_STR_RESULT(result, zbx_strdup(NULL, result->text)); /* copy line */ *p = tmp; /* restore result->text character */ } else if (0 != ZBX_ISSET_UI64(result)) { SET_STR_RESULT(result, zbx_dsprintf(NULL, ZBX_FS_UI64, result->ui64)); } else if (0 != ZBX_ISSET_DBL(result)) { SET_STR_RESULT(result, zbx_dsprintf(NULL, ZBX_FS_DBL, result->dbl)); } /* skip AR_MESSAGE - it is information field */ if (0 != ZBX_ISSET_STR(result)) return &result->str; return NULL; } static char **get_result_text_value(AGENT_RESULT *result) { assert(result); if (0 != ZBX_ISSET_TEXT(result)) { /* nothing to do */ } else if (0 != ZBX_ISSET_STR(result)) { SET_TEXT_RESULT(result, zbx_strdup(NULL, result->str)); } else if (0 != ZBX_ISSET_UI64(result)) { SET_TEXT_RESULT(result, zbx_dsprintf(NULL, ZBX_FS_UI64, result->ui64)); } else if (0 != ZBX_ISSET_DBL(result)) { SET_TEXT_RESULT(result, zbx_dsprintf(NULL, ZBX_FS_DBL, result->dbl)); } /* skip AR_MESSAGE - it is information field */ if (0 != ZBX_ISSET_TEXT(result)) return &result->text; return NULL; } static zbx_log_t *get_result_log_value(AGENT_RESULT *result) { if (0 != ZBX_ISSET_LOG(result)) return result->log; if (0 != ZBX_ISSET_VALUE(result)) { result->log = (zbx_log_t *)zbx_malloc(result->log, sizeof(zbx_log_t)); zbx_log_init(result->log); if (0 != ZBX_ISSET_STR(result)) result->log->value = zbx_strdup(result->log->value, result->str); else if (0 != ZBX_ISSET_TEXT(result)) result->log->value = zbx_strdup(result->log->value, result->text); else if (0 != ZBX_ISSET_UI64(result)) result->log->value = zbx_dsprintf(result->log->value, ZBX_FS_UI64, result->ui64); else if (0 != ZBX_ISSET_DBL(result)) result->log->value = zbx_dsprintf(result->log->value, ZBX_FS_DBL, result->dbl); result->type |= AR_LOG; return result->log; } return NULL; } /****************************************************************************** * * * Purpose: Returns value of the result in the special type. * * If value is missing, it converts existing value to requested * * type. * * * * Return value: * * NULL - if value is missing or can't be converted * * * * Comments: better use definitions * * ZBX_GET_UI64_RESULT * * ZBX_GET_DBL_RESULT * * ZBX_GET_STR_RESULT * * ZBX_GET_TEXT_RESULT * * ZBX_GET_LOG_RESULT * * ZBX_GET_MSG_RESULT * * * * AR_MESSAGE - skipped in conversion * * * ******************************************************************************/ void *get_result_value_by_type(AGENT_RESULT *result, int require_type) { assert(result); switch (require_type) { case AR_UINT64: return (void *)get_result_ui64_value(result); case AR_DOUBLE: return (void *)get_result_dbl_value(result); case AR_STRING: return (void *)get_result_str_value(result); case AR_TEXT: return (void *)get_result_text_value(result); case AR_LOG: return (void *)get_result_log_value(result); case AR_MESSAGE: if (0 != ZBX_ISSET_MSG(result)) return (void *)(&result->msg); break; default: break; } return NULL; } #if !defined(_WINDOWS) && !defined(__MINGW32__) #if defined(WITH_AGENT2_METRICS) int zbx_execute_threaded_metric(zbx_metric_func_t metric_func, AGENT_REQUEST *request, AGENT_RESULT *result) { /* calling fork() in a multithreaded program may result in deadlock on mutex */ return metric_func(request, result); } #else /******************************************************************************* * * * Purpose: serializes agent result to transfer over pipe/socket * * * * Parameters: data - [IN/OUT] data buffer * * data_alloc - [IN/OUT] data buffer allocated size * * data_offset - [IN/OUT] data buffer data size * * agent_ret - [IN] agent result return code * * result - [IN] agent result * * * * Comments: The agent result is serialized as [rc][type][data] where: * * [rc] the agent result return code, 4 bytes * * [type] the agent result data type, 1 byte * * [data] the agent result data, null terminated string (optional) * * * *******************************************************************************/ static void serialize_agent_result(char **data, size_t *data_alloc, size_t *data_offset, int agent_ret, AGENT_RESULT *result) { char **pvalue, result_type; size_t value_len; if (SYSINFO_RET_OK == agent_ret) { if (ZBX_ISSET_TEXT(result)) result_type = 't'; else if (ZBX_ISSET_STR(result)) result_type = 's'; else if (ZBX_ISSET_UI64(result)) result_type = 'u'; else if (ZBX_ISSET_DBL(result)) result_type = 'd'; else if (ZBX_ISSET_MSG(result)) result_type = 'm'; else result_type = '-'; } else result_type = 'm'; switch (result_type) { case 't': case 's': case 'u': case 'd': pvalue = ZBX_GET_TEXT_RESULT(result); break; case 'm': pvalue = ZBX_GET_MSG_RESULT(result); break; default: pvalue = NULL; } if (NULL != pvalue) { value_len = strlen(*pvalue) + 1; } else { value_len = 0; result_type = '-'; } if (*data_alloc - *data_offset < value_len + 1 + sizeof(int)) { while (*data_alloc - *data_offset < value_len + 1 + sizeof(int)) *data_alloc = (size_t)(*data_alloc * 1.5); *data = (char *)zbx_realloc(*data, *data_alloc); } memcpy(*data + *data_offset, &agent_ret, sizeof(int)); *data_offset += sizeof(int); (*data)[(*data_offset)++] = result_type; if ('-' != result_type) { memcpy(*data + *data_offset, *pvalue, value_len); *data_offset += value_len; } } /****************************************************************************** * * * Purpose: deserializes agent result * * * * Parameters: data - [IN] data to deserialize * * result - [OUT] agent result * * * * Return value: the agent result return code (SYSINFO_RET_*) * * * ******************************************************************************/ static int deserialize_agent_result(char *data, AGENT_RESULT *result) { int ret, agent_ret; char type; memcpy(&agent_ret, data, sizeof(int)); data += sizeof(int); type = *data++; if ('m' == type || 0 == strcmp(data, ZBX_NOTSUPPORTED)) { SET_MSG_RESULT(result, zbx_strdup(NULL, data)); return agent_ret; } switch (type) { case 't': ret = zbx_set_agent_result_type(result, ITEM_VALUE_TYPE_TEXT, data); break; case 's': ret = zbx_set_agent_result_type(result, ITEM_VALUE_TYPE_STR, data); break; case 'u': ret = zbx_set_agent_result_type(result, ITEM_VALUE_TYPE_UINT64, data); break; case 'd': ret = zbx_set_agent_result_type(result, ITEM_VALUE_TYPE_FLOAT, data); break; default: ret = SUCCEED; } /* return deserialized return code or SYSINFO_RET_FAIL if setting result data failed */ return (FAIL == ret ? SYSINFO_RET_FAIL : agent_ret); } /****************************************************************************** * * * Purpose: Executes metric in a separate process/thread so it can be * * killed/terminated when timeout is detected. * * * * Parameters: metric_func - [IN] metric function to execute * * ... metric function parameters * * * * Return value: * * SYSINFO_RET_OK - metric was executed successfully * * SYSINFO_RET_FAIL - otherwise * * * ******************************************************************************/ int zbx_execute_threaded_metric(zbx_metric_func_t metric_func, AGENT_REQUEST *request, AGENT_RESULT *result) { int ret = SYSINFO_RET_OK; pid_t pid; int fds[2], n, status; char buffer[MAX_STRING_LEN], *data; size_t data_alloc = MAX_STRING_LEN, data_offset = 0; zabbix_log(LOG_LEVEL_DEBUG, "In %s() key:'%s'", __func__, request->key); if (-1 == pipe(fds)) { SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot create data pipe: %s", zbx_strerror_from_system(errno))); ret = SYSINFO_RET_FAIL; goto out; } if (-1 == (pid = zbx_fork())) { close(fds[0]); close(fds[1]); SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot fork data process: %s", zbx_strerror_from_system(errno))); ret = SYSINFO_RET_FAIL; goto out; } data = (char *)zbx_malloc(NULL, data_alloc); if (0 == pid) { zabbix_log(LOG_LEVEL_DEBUG, "executing in data process for key:'%s'", request->key); zbx_set_metric_thread_signal_handler(); close(fds[0]); ret = metric_func(request, result); serialize_agent_result(&data, &data_alloc, &data_offset, ret, result); ret = zbx_write_all(fds[1], data, data_offset); zbx_free(data); zbx_free_agent_result(result); close(fds[1]); exit(SUCCEED == ret ? EXIT_SUCCESS : EXIT_FAILURE); } close(fds[1]); zbx_alarm_on(request->timeout); while (0 != (n = read(fds[0], buffer, sizeof(buffer)))) { if (SUCCEED == zbx_alarm_timed_out()) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while waiting for data.")); kill(pid, SIGKILL); ret = SYSINFO_RET_FAIL; break; } if (-1 == n) { SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Error while reading data: %s", zbx_strerror(errno))); kill(pid, SIGKILL); ret = SYSINFO_RET_FAIL; break; } if ((int)(data_alloc - data_offset) < n + 1) { while ((int)(data_alloc - data_offset) < n + 1) data_alloc = (size_t)(data_alloc * 1.5); data = (char *)zbx_realloc(data, data_alloc); } memcpy(data + data_offset, buffer, n); data_offset += n; data[data_offset] = '\0'; } zbx_alarm_off(); close(fds[0]); while (-1 == waitpid(pid, &status, 0)) { if (EINTR != errno) { zabbix_log(LOG_LEVEL_ERR, "failed to wait on child processes: %s", zbx_strerror(errno)); ret = SYSINFO_RET_FAIL; break; } } if (SYSINFO_RET_OK == ret) { if (0 == WIFEXITED(status)) { SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Data gathering process terminated unexpectedly with" " error %d.", status)); kill(pid, SIGKILL); ret = SYSINFO_RET_FAIL; } else if (EXIT_SUCCESS != WEXITSTATUS(status)) { SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Data gathering process terminated with error %d.", status)); ret = SYSINFO_RET_FAIL; } else ret = deserialize_agent_result(data, result); } zbx_free(data); out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s '%s'", __func__, zbx_sysinfo_ret_string(ret), ZBX_ISSET_MSG(result) ? result->msg : ""); return ret; } #endif /****************************************************************************** * * * Purpose: frees previously allocated mount-point structure * * * * Parameters: mpoint - [IN] pointer to structure from vector * * * ******************************************************************************/ void zbx_mpoints_free(zbx_mpoint_t *mpoint) { zbx_free(mpoint->options); zbx_free(mpoint); } int zbx_fsname_compare(const void *fs1, const void *fs2) { int res; const zbx_mpoint_t *f1 = *((const zbx_mpoint_t * const *)fs1); const zbx_fsname_t *f2 = *((const zbx_fsname_t * const *)fs2); if (0 != (res = strcmp(f1->fsname, f2->mpoint))) return res; return strcmp(f1->fstype, f2->type); } #else static ZBX_THREAD_LOCAL zbx_uint32_t mutex_flag = ZBX_MUTEX_ALL_ALLOW; zbx_uint32_t zbx_get_thread_global_mutex_flag() { return mutex_flag; } typedef struct { zbx_metric_func_t func; AGENT_REQUEST *request; AGENT_RESULT *result; zbx_uint32_t mutex_flag; /* in regular case should always be = ZBX_MUTEX_ALL_ALLOW */ HANDLE timeout_event; int agent_ret; } zbx_metric_thread_args_t; ZBX_THREAD_ENTRY(agent_metric_thread, data) { zbx_metric_thread_args_t *args = (zbx_metric_thread_args_t *)((zbx_thread_args_t *)data)->args; mutex_flag = args->mutex_flag; zabbix_log(LOG_LEVEL_DEBUG, "executing in data thread for key:'%s'", args->request->key); if (SYSINFO_RET_FAIL == (args->agent_ret = args->func(args->request, args->result, args->timeout_event))) { if (NULL == ZBX_GET_MSG_RESULT(args->result)) SET_MSG_RESULT(args->result, zbx_strdup(NULL, ZBX_NOTSUPPORTED)); } zbx_thread_exit(0); } /****************************************************************************** * * * Purpose: Executes metric in a separate process/thread so it can be * * killed/terminated when timeout is detected. * * * * Parameters: metric_func - [IN] metric function to execute * * ... metric function parameters * * * * Return value: * * SYSINFO_RET_OK - metric was executed successfully * * SYSINFO_RET_FAIL - otherwise * * * ******************************************************************************/ int zbx_execute_threaded_metric(zbx_metric_func_t metric_func, AGENT_REQUEST *request, AGENT_RESULT *result) { ZBX_THREAD_HANDLE thread; zbx_thread_args_t thread_args; zbx_metric_thread_args_t metric_args = {metric_func, request, result, ZBX_MUTEX_THREAD_DENIED | ZBX_MUTEX_LOGGING_DENIED}; DWORD rc; BOOL terminate_thread = FALSE; zabbix_log(LOG_LEVEL_DEBUG, "In %s() key:'%s'", __func__, request->key); if (NULL == (metric_args.timeout_event = CreateEvent(NULL, TRUE, FALSE, NULL))) { SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot create timeout event for data thread: %s", zbx_strerror_from_system(GetLastError()))); return SYSINFO_RET_FAIL; } thread_args.args = (void *)&metric_args; zbx_thread_start(agent_metric_thread, &thread_args, &thread); if (ZBX_THREAD_ERROR == thread) { SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot start data thread: %s", zbx_strerror_from_system(GetLastError()))); CloseHandle(metric_args.timeout_event); return SYSINFO_RET_FAIL; } /* 1000 is multiplier for converting seconds into milliseconds */ if (WAIT_FAILED == (rc = WaitForSingleObject(thread, request->timeout * 1000))) { /* unexpected error */ SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot wait for data: %s", zbx_strerror_from_system(GetLastError()))); terminate_thread = TRUE; } else if (WAIT_TIMEOUT == rc) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while waiting for data.")); /* timeout; notify thread to clean up and exit, if stuck then terminate it */ if (FALSE == SetEvent(metric_args.timeout_event)) { zabbix_log(LOG_LEVEL_ERR, "SetEvent() failed: %s", zbx_strerror_from_system(GetLastError())); terminate_thread = TRUE; } else { DWORD timeout_rc = WaitForSingleObject(thread, 3000); /* wait up to 3 seconds */ if (WAIT_FAILED == timeout_rc) { zabbix_log(LOG_LEVEL_ERR, "Waiting for data failed: %s", zbx_strerror_from_system(GetLastError())); terminate_thread = TRUE; } else if (WAIT_TIMEOUT == timeout_rc) { zabbix_log(LOG_LEVEL_ERR, "Stuck data thread"); terminate_thread = TRUE; } /* timeout_rc must be WAIT_OBJECT_0 (signaled) */ } } if (TRUE == terminate_thread) { if (FALSE != TerminateThread(thread, 0)) { zabbix_log(LOG_LEVEL_ERR, "%s(): TerminateThread() for %s[%s%s] succeeded", __func__, request->key, (0 < request->nparam) ? request->params[0] : "", (1 < request->nparam) ? ",..." : ""); } else { zabbix_log(LOG_LEVEL_ERR, "%s(): TerminateThread() for %s[%s%s] failed: %s", __func__, request->key, (0 < request->nparam) ? request->params[0] : "", (1 < request->nparam) ? ",..." : "", zbx_strerror_from_system(GetLastError())); } } CloseHandle(thread); CloseHandle(metric_args.timeout_event); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s '%s'", __func__, zbx_sysinfo_ret_string(metric_args.agent_ret), ZBX_ISSET_MSG(result) ? result->msg : ""); return WAIT_OBJECT_0 == rc ? metric_args.agent_ret : SYSINFO_RET_FAIL; } #endif #if !defined(_WINDOWS) && !defined(__MINGW32__) static void get_fqdn(char **hostname) { char buffer[MAX_STRING_LEN]; struct addrinfo hints = {0}; struct addrinfo* res = NULL; buffer[MAX_STRING_LEN - 1] = '\0'; /* check for successful call to the gethostname and check that data fits in the buffer */ if (0 == gethostname(buffer, MAX_STRING_LEN - 1) && MAX_STRING_LEN - 2 > strlen(buffer)) *hostname = zbx_strdup(*hostname, buffer); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_CANONNAME; if (0 == getaddrinfo(*hostname, 0, &hints, &res)) *hostname = zbx_strdup(*hostname, res->ai_canonname); if (NULL != res) freeaddrinfo(res); zbx_rtrim(*hostname, " .\n\r"); } int hostname_handle_params(AGENT_REQUEST *request, AGENT_RESULT *result, char **hostname) { char *type, *transform; type = get_rparam(request, 0); transform = get_rparam(request, 1); if (NULL != type && '\0' != *type && 0 != strcmp(type, "host")) { if (0 == strcmp(type, "shorthost")) { char *dot; if (NULL != (dot = strchr(*hostname, '.'))) *dot = '\0'; } else if (0 == strcmp(type, "fqdn")) { get_fqdn(hostname); } else if (0 == strcmp(type, "netbios")) { SET_MSG_RESULT(result, zbx_strdup(NULL, "NetBIOS is not supported on the current platform.")); return FAIL; } else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter.")); return FAIL; } } if (NULL != transform && '\0' != *transform && 0 != strcmp(transform, "none")) { if (0 == strcmp(transform, "lower")) { zbx_strlower(*hostname); } else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter.")); return FAIL; } } SET_STR_RESULT(result, *hostname); return SUCCEED; } /****************************************************************************** * * * Purpose: formats string containing human-readable mount options from flags * * * * Parameters: mntopts - [IN] array containing flag to string mappings * * flags - [IN] mount point flags * * * * Return value: returns null-terminated allocated string with * * comma-separated mount options * * * ******************************************************************************/ char *zbx_format_mntopt_string(zbx_mntopt_t mntopts[], int flags) { char *dst_string = NULL; size_t dst_alloc = 1, dst_offset = 0; zbx_mntopt_t *mntopt; dst_string = (char *)zbx_malloc(NULL, dst_alloc); *dst_string = '\0'; if (0 != flags) { for (mntopt = mntopts; 0 != mntopt->flag; mntopt++) { if (0 == ((zbx_uint64_t)flags & mntopt->flag)) continue; if ('\0' != *dst_string) zbx_strcpy_alloc(&dst_string, &dst_alloc, &dst_offset, ","); zbx_strcpy_alloc(&dst_string, &dst_alloc, &dst_offset, mntopt->name); } } return dst_string; } #endif int zbx_validate_item_timeout(const char *timeout_str, int *sec_out, char *error, size_t error_len) { #define ZBX_ITEM_TIMEOUT_MAX 600 int sec; if (SUCCEED != zbx_is_time_suffix(timeout_str, &sec, ZBX_LENGTH_UNLIMITED) || ZBX_ITEM_TIMEOUT_MAX < sec || 1 > sec) { zbx_strlcpy(error, "Unsupported timeout value.", error_len); return FAIL; } if (NULL != sec_out) *sec_out = sec; return SUCCEED; #undef ZBX_ITEM_TIMEOUT_MAX }