/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ #include "nodecommand.h" #include "zbxserver.h" #include "log.h" #include "trapper_auth.h" #include "../scripts/scripts.h" #include "audit/zbxaudit.h" #include "../../libs/zbxserver/get_host_from_event.h" #include "../../libs/zbxserver/zabbix_users.h" #include "zbxdbwrap.h" #include "zbx_trigger_constants.h" /****************************************************************************** * * * Purpose: execute remote command and wait for the result * * * * Return value: SUCCEED - the remote command was executed successfully * * FAIL - an error occurred * * * ******************************************************************************/ static int execute_remote_script(const zbx_script_t *script, const DC_HOST *host, char **info, char *error, size_t max_error_len) { int time_start; zbx_uint64_t taskid; DB_RESULT result = NULL; DB_ROW row; if (0 == (taskid = zbx_script_create_task(script, host, 0, time(NULL)))) { zbx_snprintf(error, max_error_len, "Cannot create remote command task."); return FAIL; } for (time_start = time(NULL); SEC_PER_MIN > time(NULL) - time_start; sleep(1)) { result = zbx_db_select( "select tr.status,tr.info" " from task t" " left join task_remote_command_result tr" " on tr.taskid=t.taskid" " where tr.parent_taskid=" ZBX_FS_UI64, taskid); if (NULL != (row = zbx_db_fetch(result))) { int ret; if (SUCCEED == (ret = atoi(row[0]))) *info = zbx_strdup(*info, row[1]); else zbx_strlcpy(error, row[1], max_error_len); zbx_db_free_result(result); return ret; } zbx_db_free_result(result); } zbx_snprintf(error, max_error_len, "Timeout while waiting for remote command result."); return FAIL; } static int zbx_get_script_details(zbx_uint64_t scriptid, zbx_script_t *script, int *scope, zbx_uint64_t *usrgrpid, zbx_uint64_t *groupid, char *error, size_t error_len) { int ret = FAIL; DB_RESULT db_result; DB_ROW row; zbx_uint64_t usrgrpid_l, groupid_l; db_result = zbx_db_select("select command,host_access,usrgrpid,groupid,type,execute_on,timeout,scope,port,authtype" ",username,password,publickey,privatekey" " from scripts" " where scriptid=" ZBX_FS_UI64, scriptid); if (NULL == db_result) { zbx_strlcpy(error, "Cannot select from table 'scripts'.", error_len); return FAIL; } if (NULL == (row = zbx_db_fetch(db_result))) { zbx_strlcpy(error, "Script not found.", error_len); goto fail; } ZBX_DBROW2UINT64(usrgrpid_l, row[2]); *usrgrpid = usrgrpid_l; ZBX_DBROW2UINT64(groupid_l, row[3]); *groupid = groupid_l; ZBX_STR2UCHAR(script->type, row[4]); if (ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT == script->type) ZBX_STR2UCHAR(script->execute_on, row[5]); if (ZBX_SCRIPT_TYPE_SSH == script->type) { ZBX_STR2UCHAR(script->authtype, row[9]); script->publickey = zbx_strdup(script->publickey, row[12]); script->privatekey = zbx_strdup(script->privatekey, row[13]); } if (ZBX_SCRIPT_TYPE_SSH == script->type || ZBX_SCRIPT_TYPE_TELNET == script->type) { script->port = zbx_strdup(script->port, row[8]); script->username = zbx_strdup(script->username, row[10]); script->password = zbx_strdup(script->password, row[11]); } script->command = zbx_strdup(script->command, row[0]); script->command_orig = zbx_strdup(script->command_orig, row[0]); script->scriptid = scriptid; ZBX_STR2UCHAR(script->host_access, row[1]); if (SUCCEED != zbx_is_time_suffix(row[6], &script->timeout, ZBX_LENGTH_UNLIMITED)) { zbx_strlcpy(error, "Invalid timeout value in script configuration.", error_len); goto fail; } *scope = atoi(row[7]); ret = SUCCEED; fail: zbx_db_free_result(db_result); return ret; } static int is_user_in_allowed_group(zbx_uint64_t userid, zbx_uint64_t usrgrpid, char *error, size_t error_len) { DB_RESULT result; int ret = FAIL; result = zbx_db_select("select null" " from users_groups" " where usrgrpid=" ZBX_FS_UI64 " and userid=" ZBX_FS_UI64, usrgrpid, userid); if (NULL == result) { zbx_strlcpy(error, "Database error, cannot get user rights.", error_len); goto fail; } if (NULL == zbx_db_fetch(result)) zbx_strlcpy(error, "User has no rights to execute this script.", error_len); else ret = SUCCEED; zbx_db_free_result(result); fail: return ret; } /****************************************************************************** * * * Purpose: check if the specified event id corresponds to a problem event * * caused by a trigger, find its recovery event (if it exists) * * * * Parameters: eventid - [IN] the id of event * * r_eventid - [OUT] the id of recovery event (0 if there is * * no recovery event * * error - [OUT] the error message buffer * * error_len - [IN] the size of error message buffer * * * * Return value: SUCCEED or FAIL (with 'error' message) * * * ******************************************************************************/ static int zbx_check_event_end_recovery_event(zbx_uint64_t eventid, zbx_uint64_t *r_eventid, char *error, size_t error_len) { DB_RESULT db_result; DB_ROW row; if (NULL == (db_result = zbx_db_select("select r_eventid from event_recovery where eventid="ZBX_FS_UI64, eventid))) { zbx_strlcpy(error, "Database error, cannot read from 'events' and 'event_recovery' tables.", error_len); return FAIL; } if (NULL == (row = zbx_db_fetch(db_result))) *r_eventid = 0; else ZBX_DBROW2UINT64(*r_eventid, row[0]); zbx_db_free_result(db_result); return SUCCEED; } /****************************************************************************** * * * Purpose: executing command * * * * Parameters: scriptid - [IN] the id of a script to be executed * * hostid - [IN] the host the script will be executed on * * eventid - [IN] the id of an event * * user - [IN] the user who executes the command * * clientip - [IN] the IP of client * * config_timeout - [IN] * * result - [OUT] the result of a script execution * * debug - [OUT] the debug data (optional) * * * * Return value: SUCCEED - processed successfully * * FAIL - an error occurred * * * * Comments: either 'hostid' or 'eventid' must be > 0, but not both * * * ******************************************************************************/ static int execute_script(zbx_uint64_t scriptid, zbx_uint64_t hostid, zbx_uint64_t eventid, zbx_user_t *user, const char *clientip, int config_timeout, char **result, char **debug) { int ret = FAIL, scope = 0, i, macro_type; DC_HOST host; zbx_script_t script; zbx_uint64_t usrgrpid, groupid; zbx_vector_uint64_t eventids; zbx_vector_ptr_t events; zbx_vector_ptr_pair_t webhook_params; char *user_timezone = NULL, *webhook_params_json = NULL, error[MAX_STRING_LEN]; zbx_db_event *problem_event = NULL, *recovery_event = NULL; zbx_dc_um_handle_t *um_handle = NULL; zabbix_log(LOG_LEVEL_DEBUG, "In %s() scriptid:" ZBX_FS_UI64 " hostid:" ZBX_FS_UI64 " eventid:" ZBX_FS_UI64 " userid:" ZBX_FS_UI64 " clientip:%s", __func__, scriptid, hostid, eventid, user->userid, clientip); *error = '\0'; memset(&host, 0, sizeof(host)); zbx_vector_uint64_create(&eventids); zbx_vector_ptr_create(&events); zbx_vector_ptr_pair_create(&webhook_params); zbx_script_init(&script); if (SUCCEED != zbx_get_script_details(scriptid, &script, &scope, &usrgrpid, &groupid, error, sizeof(error))) goto fail; /* validate script permissions */ if (0 < usrgrpid && USER_TYPE_SUPER_ADMIN != user->type && SUCCEED != is_user_in_allowed_group(user->userid, usrgrpid, error, sizeof(error))) { goto fail; } if (0 != hostid) { if (ZBX_SCRIPT_SCOPE_HOST != scope) { zbx_snprintf(error, sizeof(error), "Script is not allowed in manual host action: scope:%d", scope); goto fail; } } else if (ZBX_SCRIPT_SCOPE_EVENT != scope) { zbx_snprintf(error, sizeof(error), "Script is not allowed in manual event action: scope:%d", scope); goto fail; } /* get host or event details */ if (0 != hostid) { if (SUCCEED != DCget_host_by_hostid(&host, hostid)) { zbx_strlcpy(error, "Unknown host identifier.", sizeof(error)); goto fail; } } else /* eventid */ { zbx_uint64_t r_eventid; if (SUCCEED != zbx_check_event_end_recovery_event(eventid, &r_eventid, error, sizeof(error))) goto fail; zbx_vector_uint64_reserve(&eventids, 2); zbx_vector_ptr_reserve(&events, 2); zbx_vector_uint64_append(&eventids, eventid); /* problem event in element [0]*/ if (0 != r_eventid) /* optional recovery event in element [1] */ zbx_vector_uint64_append(&eventids, r_eventid); zbx_db_get_events_by_eventids(&eventids, &events); if (events.values_num != eventids.values_num) { zbx_strlcpy(error, "Specified event data not found.", sizeof(error)); goto fail; } switch (events.values_num) { case 1: if (eventid == ((zbx_db_event *)(events.values[0]))->eventid) { problem_event = events.values[0]; } else { zbx_strlcpy(error, "Specified event data not found.", sizeof(error)); goto fail; } break; case 2: if (r_eventid == ((zbx_db_event *)(events.values[0]))->eventid) { problem_event = events.values[1]; recovery_event = events.values[0]; } else { problem_event = events.values[0]; recovery_event = events.values[1]; } break; default: THIS_SHOULD_NEVER_HAPPEN; zbx_snprintf(error, sizeof(error), "Internal error in %s() events.values_num:%d", __func__, events.values_num); goto fail; } if (EVENT_SOURCE_TRIGGERS != problem_event->source) { zbx_strlcpy(error, "The source of specified event is not a trigger.", sizeof(error)); goto fail; } if (TRIGGER_VALUE_PROBLEM != problem_event->value) { zbx_strlcpy(error, "The specified event is not a problem event.", sizeof(error)); goto fail; } if (SUCCEED != get_host_from_event((NULL != recovery_event) ? recovery_event : problem_event, &host, error, sizeof(error))) { goto fail; } } if (SUCCEED != zbx_check_script_permissions(groupid, host.hostid)) { zbx_strlcpy(error, "Script does not have permission to be executed on the host.", sizeof(error)); goto fail; } if (USER_TYPE_SUPER_ADMIN != user->type && SUCCEED != zbx_check_script_user_permissions(user->userid, host.hostid, &script)) { zbx_strlcpy(error, "User does not have permission to execute this script on the host.", sizeof(error)); goto fail; } user_timezone = get_user_timezone(user->userid); /* substitute macros in script body and webhook parameters */ if (0 != hostid) /* script on host */ macro_type = MACRO_TYPE_SCRIPT; else macro_type = (NULL != recovery_event) ? MACRO_TYPE_SCRIPT_RECOVERY : MACRO_TYPE_SCRIPT_NORMAL; um_handle = zbx_dc_open_user_macros(); if (ZBX_SCRIPT_TYPE_WEBHOOK != script.type) { if (SUCCEED != zbx_substitute_simple_macros_unmasked(NULL, problem_event, recovery_event, &user->userid, NULL, &host, NULL, NULL, NULL, NULL, NULL, user_timezone, &script.command, macro_type, error, sizeof(error))) { goto fail; } if (SUCCEED != zbx_substitute_simple_macros(NULL, problem_event, recovery_event, &user->userid, NULL, &host, NULL, NULL, NULL, NULL, NULL, user_timezone, &script.command_orig, macro_type, error, sizeof(error))) { THIS_SHOULD_NEVER_HAPPEN; goto fail; } } else { if (SUCCEED != DBfetch_webhook_params(script.scriptid, &webhook_params, error, sizeof(error))) goto fail; for (i = 0; i < webhook_params.values_num; i++) { if (SUCCEED != zbx_substitute_simple_macros_unmasked(NULL, problem_event, recovery_event, &user->userid, NULL, &host, NULL, NULL, NULL, NULL, NULL, user_timezone, (char **)&webhook_params.values[i].second, macro_type, error, sizeof(error))) { goto fail; } } zbx_webhook_params_pack_json(&webhook_params, &webhook_params_json); } if (SUCCEED == (ret = zbx_script_prepare(&script, &host.hostid, error, sizeof(error)))) { const char *poutput = NULL, *perror = NULL; int audit_res; if (0 == host.proxy_hostid || ZBX_SCRIPT_EXECUTE_ON_SERVER == script.execute_on || ZBX_SCRIPT_TYPE_WEBHOOK == script.type) { ret = zbx_script_execute(&script, &host, webhook_params_json, config_timeout, result, error, sizeof(error), debug); } else ret = execute_remote_script(&script, &host, result, error, sizeof(error)); if (SUCCEED == ret) poutput = *result; else perror = error; audit_res = zbx_auditlog_global_script(script.type, script.execute_on, script.command_orig, host.hostid, host.name, eventid, host.proxy_hostid, user->userid, user->username, clientip, poutput, perror); /* At the moment, there is no special processing of audit failures. */ /* It can fail only due to the DB errors and those are visible in */ /* the log anyway */ ZBX_UNUSED(audit_res); } fail: if (NULL != um_handle) zbx_dc_close_user_macros(um_handle); if (SUCCEED != ret) *result = zbx_strdup(*result, error); zbx_script_clean(&script); zbx_free(webhook_params_json); zbx_free(user_timezone); for (i = 0; i < webhook_params.values_num; i++) { zbx_free(webhook_params.values[i].first); zbx_free(webhook_params.values[i].second); } zbx_vector_ptr_pair_destroy(&webhook_params); zbx_vector_ptr_clear_ext(&events, (zbx_clean_func_t)zbx_db_free_event); zbx_vector_ptr_destroy(&events); zbx_vector_uint64_destroy(&eventids); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } /****************************************************************************** * * * Purpose: process command received from the frontend * * * * Return value: SUCCEED - processed successfully * * FAIL - an error occurred * * * ******************************************************************************/ int node_process_command(zbx_socket_t *sock, const char *data, const struct zbx_json_parse *jp, int config_timeout) { char *result = NULL, *send = NULL, *debug = NULL, tmp[64], tmp_hostid[64], tmp_eventid[64], clientip[MAX_STRING_LEN]; int ret = FAIL, got_hostid = 0, got_eventid = 0; zbx_uint64_t scriptid, hostid = 0, eventid = 0; struct zbx_json j; zbx_user_t user; zabbix_log(LOG_LEVEL_DEBUG, "In %s(): data:%s ", __func__, data); zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN); zbx_user_init(&user); /* check who is connecting, get user details, check access rights */ if (FAIL == zbx_get_user_from_json(jp, &user, &result)) goto finish; if (SUCCEED != check_perm2system(user.userid)) { result = zbx_strdup(result, "Permission denied. User is a member of group with disabled access."); goto finish; } #define ZBX_USER_ROLE_PERMISSION_ACTIONS_DEFAULT_ACCESS "actions.default_access" #define ZBX_USER_ROLE_PERMISSION_ACTIONS_EXECUTE_SCRIPTS "actions.execute_scripts" if (SUCCEED != zbx_check_user_administration_actions_permissions(&user, ZBX_USER_ROLE_PERMISSION_ACTIONS_DEFAULT_ACCESS, ZBX_USER_ROLE_PERMISSION_ACTIONS_EXECUTE_SCRIPTS)) { result = zbx_strdup(result, "Permission denied. No role access."); goto finish; } #undef ZBX_USER_ROLE_PERMISSION_ACTIONS_DEFAULT_ACCESS #undef ZBX_USER_ROLE_PERMISSION_ACTIONS_EXECUTE_SCRIPTS /* extract and validate other JSON elements */ if (SUCCEED != zbx_json_value_by_name(jp, ZBX_PROTO_TAG_SCRIPTID, tmp, sizeof(tmp), NULL) || FAIL == zbx_is_uint64(tmp, &scriptid)) { result = zbx_dsprintf(result, "Failed to parse command request tag: %s.", ZBX_PROTO_TAG_SCRIPTID); goto finish; } if (SUCCEED == zbx_json_value_by_name(jp, ZBX_PROTO_TAG_HOSTID, tmp_hostid, sizeof(tmp_hostid), NULL)) got_hostid = 1; if (SUCCEED == zbx_json_value_by_name(jp, ZBX_PROTO_TAG_EVENTID, tmp_eventid, sizeof(tmp_eventid), NULL)) got_eventid = 1; if (0 == got_hostid && 0 == got_eventid) { result = zbx_dsprintf(result, "Failed to parse command request tag %s or %s.", ZBX_PROTO_TAG_HOSTID, ZBX_PROTO_TAG_EVENTID); goto finish; } if (1 == got_hostid && 1 == got_eventid) { result = zbx_dsprintf(result, "Command request tags %s and %s cannot be used together.", ZBX_PROTO_TAG_HOSTID, ZBX_PROTO_TAG_EVENTID); goto finish; } if (1 == got_hostid) { if (SUCCEED != zbx_is_uint64(tmp_hostid, &hostid)) { result = zbx_dsprintf(result, "Failed to parse value of command request tag %s.", ZBX_PROTO_TAG_HOSTID); goto finish; } if (0 == hostid) { result = zbx_dsprintf(result, "%s value cannot be 0.", ZBX_PROTO_TAG_HOSTID); goto finish; } } else { if (SUCCEED != zbx_is_uint64(tmp_eventid, &eventid)) { result = zbx_dsprintf(result, "Failed to parse value of command request tag %s.", ZBX_PROTO_TAG_EVENTID); goto finish; } if (0 == eventid) { result = zbx_dsprintf(result, "%s value cannot be 0.", ZBX_PROTO_TAG_EVENTID); goto finish; } } if (SUCCEED != zbx_json_value_by_name(jp, ZBX_PROTO_TAG_CLIENTIP, clientip, sizeof(clientip), NULL)) *clientip = '\0'; if (SUCCEED == (ret = execute_script(scriptid, hostid, eventid, &user, clientip, config_timeout, &result, &debug))) { zbx_json_addstring(&j, ZBX_PROTO_TAG_RESPONSE, ZBX_PROTO_VALUE_SUCCESS, ZBX_JSON_TYPE_STRING); zbx_json_addstring(&j, ZBX_PROTO_TAG_DATA, result, ZBX_JSON_TYPE_STRING); if (NULL != debug) zbx_json_addraw(&j, "debug", debug); send = j.buffer; } finish: if (SUCCEED != ret) { zbx_json_addstring(&j, ZBX_PROTO_TAG_RESPONSE, ZBX_PROTO_VALUE_FAILED, ZBX_JSON_TYPE_STRING); zbx_json_addstring(&j, ZBX_PROTO_TAG_INFO, (NULL != result ? result : "Unknown error."), ZBX_JSON_TYPE_STRING); send = j.buffer; } zbx_alarm_on(config_timeout); if (SUCCEED != zbx_tcp_send(sock, send)) zabbix_log(LOG_LEVEL_WARNING, "Error sending result of command"); else zabbix_log(LOG_LEVEL_DEBUG, "Sending back command '%s' result '%s'", data, send); zbx_alarm_off(); zbx_json_free(&j); zbx_free(result); zbx_free(debug); zbx_user_free(&user); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; }