/* ** 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/>. **/ /* strptime() on newer and older GNU/Linux systems */ #define _GNU_SOURCE #include "macrofunc.h" #include "zbxexpression.h" #include "zbxregexp.h" #include "zbxnum.h" #include "zbxstr.h" #include "zbxtime.h" typedef struct { const char *macro; const char *functions; } zbx_macro_functions_t; #include "zbxcrypto.h" #define ZBX_RULE_BUFF_LEN 512 typedef struct { char character; const char* html_entity; } zbx_htmlentity_t; static const zbx_htmlentity_t zbx_html_translation[] = { {'&', "&"}, {'"', """}, {'\'', "'"}, {'<', "<"}, {'>', ">"}, {'\'', "'"} }; /****************************************************************************** * * * Purpose: calculates regular expression substitution. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * FAIL - the function calculation failed * * * ******************************************************************************/ static int macrofunc_regsub(char **params, size_t nparam, char **out) { char *value = NULL; if (2 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } if (FAIL == zbx_regexp_sub(*out, params[0], params[1], &value)) return FAIL; if (NULL == value) value = zbx_strdup(NULL, ""); zbx_free(*out); *out = value; return SUCCEED; } /****************************************************************************** * * * Purpose: fills rules array according to parameter. * * * * Parameters: param - [IN] function parameter * * out - [OUT] calculated result array of rules * * * * Return value: length of output value * * FAIL - the function calculation failed * * * ******************************************************************************/ static int zbx_tr_rule_create(const unsigned char *param, unsigned char *dst) { unsigned char c, range_from = 0; const unsigned char *ptr; int i, len = 0; /* construct string to replace */ for (ptr = param; '\0' != *ptr; ptr++) { if ('\\' == *ptr) { ptr++; switch (*ptr) { case '\0': dst[len++] = '\\'; return len; case '\\': c = '\\'; break; case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; default: c = *ptr; break; } if ('-' == *(ptr + 1) && '\0' != *(ptr + 2) && 0 == range_from) { range_from = c; ptr++; /* continue allow last range char to be escaped */ continue; } } else { c = *ptr; if ('-' == *(ptr + 1) && '\0' != *(ptr + 2) && 0 == range_from) { range_from = c; ptr++; /* continue allow last range char to be escaped */ continue; } } if ('\0' != range_from) { if (range_from > c) { zabbix_log(LOG_LEVEL_DEBUG, "%s() range-endpoints are in reverse collating sequence order: %s", __func__, param); return FAIL; } for (i = 0; i < c - range_from + 1; i++) { dst[len++] = range_from + i; if (ZBX_RULE_BUFF_LEN <= len) { zabbix_log(LOG_LEVEL_DEBUG, "%s() too big parameter rule: %s", __func__, param); return FAIL; } } range_from = 0; } else { dst[len++] = c; if (ZBX_RULE_BUFF_LEN <= len) { zabbix_log(LOG_LEVEL_DEBUG, "%s() parameter rule overflow %s", __func__, param); return FAIL; } } } return len; } /****************************************************************************** * * * Purpose: calculates translation expression. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * FAIL - the function calculation failed * * * ******************************************************************************/ static int macrofunc_tr(char **params, size_t nparam, char **out) { unsigned char translate[UCHAR_MAX+1], *ptr, buff_from[ZBX_RULE_BUFF_LEN], buff_to[ZBX_RULE_BUFF_LEN]; int buff_from_len, buff_to_len; size_t i; if (2 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } if (FAIL == (buff_from_len = zbx_tr_rule_create((unsigned char*)params[0], buff_from))) return FAIL; if (FAIL == (buff_to_len = zbx_tr_rule_create((unsigned char*)params[1], buff_to))) return FAIL; if (0 == buff_from_len || 0 == buff_to_len) return SUCCEED; /* prepare and fill translation table according rules */ for (i = 0; i < sizeof(translate); i++) translate[i] = i; for (i = 0; i < (size_t)buff_from_len; i++) { if (i < (size_t)buff_to_len) translate[buff_from[i]] = buff_to[i]; else translate[buff_from[i]] = buff_to[buff_to_len - 1]; } /* translate */ ptr = (unsigned char*)*out; while ('\0' != *ptr) { *ptr = translate[*ptr]; ptr++; } zbx_replace_invalid_utf8(*out); return SUCCEED; } /****************************************************************************** * * * Purpose: zbx_base64_encode wrapper. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * * ******************************************************************************/ static int macrofunc_btoa(char **params, size_t nparam, char **out) { char *buffer = NULL; ZBX_UNUSED(params); if (0 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } zbx_base64_encode_dyn(*out, &buffer, strlen(*out)); zbx_free(*out); *out = buffer; return SUCCEED; } /****************************************************************************** * * * Purpose: zbx_url_encode wrapper. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * * ******************************************************************************/ static int macrofunc_urlencode(char **params, size_t nparam, char **out) { ZBX_UNUSED(params); if (0 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } zbx_url_encode(*out, out); return SUCCEED; } /****************************************************************************** * * * Purpose: zbx_url_decode wrapper. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * * ******************************************************************************/ static int macrofunc_urldecode(char **params, size_t nparam, char **out) { ZBX_UNUSED(params); if (0 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } zbx_url_decode(*out, out); return SUCCEED; } /****************************************************************************** * * * Purpose: zbx_strlower wrapper. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * * ******************************************************************************/ static int macrofunc_lowercase(char **params, size_t nparam, char **out) { ZBX_UNUSED(params); if (0 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } zbx_strlower(*out); return SUCCEED; } /****************************************************************************** * * * Purpose: zbx_strupper wrapper. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * * ******************************************************************************/ static int macrofunc_uppercase(char **params, size_t nparam, char **out) { ZBX_UNUSED(params); if (0 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } zbx_strupper(*out); return SUCCEED; } /****************************************************************************** * * * Purpose: Converts a string into an HTML-encoded string. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * * ******************************************************************************/ static int macrofunc_htmlencode(char **params, size_t nparam, char **out) { zbx_htmlentity_t *pentity; char ch; ZBX_UNUSED(params); if (0 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } for (size_t idx = 0; '\0' != (*out)[idx]; idx++) { for (pentity = (zbx_htmlentity_t *)zbx_html_translation; pentity < zbx_html_translation + ARRSIZE(zbx_html_translation); pentity++) { ch = pentity->character; if ((*out)[idx] == ch) { zbx_replace_string(out, idx, &idx, pentity->html_entity); break; } } } return SUCCEED; } /****************************************************************************** * * * Purpose: Converts HTML-encoded string into a decoded string. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * * ******************************************************************************/ static int macrofunc_htmldecode(char **params, size_t nparam, char **out) { char *entity, *ptr; ZBX_UNUSED(params); if (0 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } for (ptr = *out; '\0' != *ptr; ptr++) { if ('&' == *ptr) { for (size_t i = 0; i < ARRSIZE(zbx_html_translation); i++) { entity = (char *)zbx_html_translation[i].html_entity; if (0 == strncmp(ptr, entity, strlen(entity))) { *ptr = zbx_html_translation[i].character; memmove(ptr + 1, ptr + strlen(entity), strlen(ptr + strlen(entity)) + 1); break; } } } } return SUCCEED; } /****************************************************************************** * * * Purpose: calculates case insensitive regular expression replacement. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * * ******************************************************************************/ static int macrofunc_regrepl(char **params, size_t nparam, char **out) { char *value = NULL; size_t i; if (0 == nparam || 0 != nparam % 2) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } for (i = 0; i < nparam; i += 2) { int ret; ret = zbx_regexp_repl(*out, params[i], params[i + 1], &value); zbx_free(*out); *out = value; value = NULL; if (FAIL == ret) return FAIL; } return SUCCEED; } /****************************************************************************** * * * Purpose: calculates case insensitive regular expression substitution. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * FAIL - function calculation failed * * * ******************************************************************************/ static int macrofunc_iregsub(char **params, size_t nparam, char **out) { char *value = NULL; if (2 != nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } if (FAIL == zbx_iregexp_sub(*out, params[0], params[1], &value)) return FAIL; if (NULL == value) value = zbx_strdup(NULL, ""); zbx_free(*out); *out = value; return SUCCEED; } /****************************************************************************** * * * Purpose: time formatting macro function. * * * * Parameters: params - [IN] function parameters * * nparam - [IN] function parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - the function was calculated successfully * * FAIL - the function calculation failed * * * ******************************************************************************/ static int macrofunc_fmttime(char **params, size_t nparam, char **out) { struct tm local_time; time_t time_new; char *buf = NULL; if (0 == nparam || 2 < nparam) { zabbix_log(LOG_LEVEL_DEBUG, "%s() invalid parameters number", __func__); return FAIL; } time_new = time(&time_new); localtime_r(&time_new, &local_time); if (NULL == strptime(*out, "%H:%M:%S", &local_time) && NULL == strptime(*out, "%Y-%m-%dT%H:%M:%S", &local_time) && NULL == strptime(*out, "%Y-%m-%dT%H:%M:%S%z", &local_time)) { if (0 == (time_new = atoi(*out))) return FAIL; localtime_r(&time_new, &local_time); } if (2 == nparam) { char *p = params[1]; size_t len; while ('\0' != *p) { zbx_time_unit_t unit; if ('/' == *p) { if (ZBX_TIME_UNIT_UNKNOWN == (unit = zbx_tm_str_to_unit(++p))) { zabbix_log(LOG_LEVEL_DEBUG, "unexpected character starting with \"%s\"", p); return FAIL; } zbx_tm_round_down(&local_time, unit); p++; } else if ('+' == *p || '-' == *p) { int num; char op, *error = NULL; op = *(p++); if (FAIL == zbx_tm_parse_period(p, &len, &num, &unit, &error)) { zabbix_log(LOG_LEVEL_DEBUG, "failed to parse time period: %s", error); zbx_free(error); return FAIL; } if ('+' == op) zbx_tm_add(&local_time, num, unit); else zbx_tm_sub(&local_time, num, unit); p += len; } else { zabbix_log(LOG_LEVEL_DEBUG, "unexpected character starting with \"%s\"", p); return FAIL; } } } buf = zbx_malloc(NULL, MAX_STRING_LEN); if (0 == strftime(buf, MAX_STRING_LEN, params[0], &local_time)) { zabbix_log(LOG_LEVEL_DEBUG, "invalid first parameter \"%s\"", params[0]); zbx_free(buf); return FAIL; } zbx_free(*out); *out = buf; return SUCCEED; } /****************************************************************************** * * * Purpose: number formatting macro function. * * * * Parameters: params - [IN] function data * * nparam - [IN] parameter count * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - function was calculated successfully * * FAIL - function calculation failed * * * ******************************************************************************/ static int macrofunc_fmtnum(char **params, size_t nparam, char **out) { double value; int precision; if (1 != nparam) return FAIL; if (SUCCEED == zbx_is_uint32(*out, &value)) return SUCCEED; if (FAIL == zbx_is_double(*out, &value)) { zabbix_log(LOG_LEVEL_DEBUG, "macro \"%s\" is not a number", *out); return FAIL; } if (FAIL == zbx_is_uint_range(params[0], &precision, 0, 20)) { zabbix_log(LOG_LEVEL_DEBUG, "invalid parameter \"%s\"", params[0]); return FAIL; } *out = zbx_dsprintf(*out, "%.*f", precision, value); return SUCCEED; } /****************************************************************************** * * * Purpose: gets macro from the macro function * * * * Parameters: str - [IN] string containing potential macro * * fm - [IN] function macro to check * * N_functionid - [OUT] index of the macro in string (if valid) * * * * Return value: unindexed macro or NULL. * * Comments: allocates memory * * * ******************************************************************************/ char *func_get_macro_from_func(const char *str, zbx_token_func_macro_t *fm, int *N_functionid) { const char *ptr_l = str + fm->macro.l, *ptr_r; char *ptr = NULL; if (NULL != (ptr_r = strchr(ptr_l, '}'))) { size_t len = (size_t)(ptr_r - ptr_l), fm_len = fm->macro.r - fm->macro.l + 1; ptr = zbx_strdup(ptr, ptr_l); if ('?' != ptr_l[1] && len != fm_len) { if (SUCCEED == zbx_is_uint_n_range(str + fm->macro.l + len - 1, fm_len - len, N_functionid, sizeof(*N_functionid), 1, 9)) { len--; ptr[len] = '}'; } } ptr[len + 1] = '\0'; } return ptr; } /****************************************************************************** * * * Purpose: calculates macro function value. * * * * Parameters: expression - [IN] expression containing macro function * * func_macro - [IN] information about macro function token * * out - [IN/OUT] input/output value * * * * Return value: SUCCEED - the function was calculated successfully * * FAIL - the function calculation failed * * * ******************************************************************************/ int zbx_calculate_macro_function(const char *expression, const zbx_token_func_macro_t *func_macro, char **out) { char **params; const char *ptr; size_t nparam = 0, param_alloc = 8, len, sep_pos = 0; int (*macrofunc)(char **params, size_t nparam, char **out), ret = FAIL; zbx_strloc_t loc = func_macro->func_param; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); if (NULL == *out) goto out; ptr = expression + func_macro->func.l; len = func_macro->func_param.l - func_macro->func.l; if (ZBX_CONST_STRLEN("regsub") == len && 0 == strncmp(ptr, "regsub", len)) macrofunc = macrofunc_regsub; else if (ZBX_CONST_STRLEN("iregsub") == len && 0 == strncmp(ptr, "iregsub", len)) macrofunc = macrofunc_iregsub; else if (ZBX_CONST_STRLEN("fmttime") == len && 0 == strncmp(ptr, "fmttime", len)) macrofunc = macrofunc_fmttime; else if (ZBX_CONST_STRLEN("fmtnum") == len && 0 == strncmp(ptr, "fmtnum", len)) macrofunc = macrofunc_fmtnum; else if (ZBX_CONST_STRLEN("tr") == len && 0 == strncmp(ptr, "tr", len)) macrofunc = macrofunc_tr; else if (ZBX_CONST_STRLEN("btoa") == len && 0 == strncmp(ptr, "btoa", len)) macrofunc = macrofunc_btoa; else if (ZBX_CONST_STRLEN("urlencode") == len && 0 == strncmp(ptr, "urlencode", len)) macrofunc = macrofunc_urlencode; else if (ZBX_CONST_STRLEN("urldecode") == len && 0 == strncmp(ptr, "urldecode", len)) macrofunc = macrofunc_urldecode; else if (ZBX_CONST_STRLEN("lowercase") == len && 0 == strncmp(ptr, "lowercase", len)) macrofunc = macrofunc_lowercase; else if (ZBX_CONST_STRLEN("uppercase") == len && 0 == strncmp(ptr, "uppercase", len)) macrofunc = macrofunc_uppercase; else if (ZBX_CONST_STRLEN("htmlencode") == len && 0 == strncmp(ptr, "htmlencode", len)) macrofunc = macrofunc_htmlencode; else if (ZBX_CONST_STRLEN("htmldecode") == len && 0 == strncmp(ptr, "htmldecode", len)) macrofunc = macrofunc_htmldecode; else if (ZBX_CONST_STRLEN("regrepl") == len && 0 == strncmp(ptr, "regrepl", len)) macrofunc = macrofunc_regrepl; else goto out; params = (char **)zbx_malloc(NULL, sizeof(char *) * param_alloc); for (ptr = expression + loc.l + 1; ptr <= expression + loc.r; ptr += sep_pos + 1) { size_t param_pos, param_len; int quoted; if (nparam == param_alloc) { param_alloc *= 2; params = (char **)zbx_realloc(params, sizeof(char *) * param_alloc); } zbx_function_param_parse(ptr, ¶m_pos, ¶m_len, &sep_pos); if (')' == ptr[sep_pos] && 0 == nparam && 0 == param_len) break; params[nparam++] = zbx_function_param_unquote_dyn_compat(ptr + param_pos, param_len, "ed); } ret = macrofunc(params, nparam, out); while (0 < nparam--) zbx_free(params[nparam]); zbx_free(params); out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s(), ret: %s", __func__, zbx_result_string(ret)); return ret; }