1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22/** 23 * Class containing methods for operations with scripts. 24 */ 25class CScript extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 29 'getscriptsbyhosts' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 30 'create' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 31 'update' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 32 'delete' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 33 'execute' => ['min_user_type' => USER_TYPE_ZABBIX_USER, 'action' => CRoleHelper::ACTIONS_EXECUTE_SCRIPTS] 34 ]; 35 36 protected $tableName = 'scripts'; 37 protected $tableAlias = 's'; 38 protected $sortColumns = ['scriptid', 'name']; 39 40 /** 41 * Fields from "actions" table. Used in get() validation and addRelatedObjects() when selecting action fields. 42 */ 43 private $action_fields = ['actionid', 'name', 'eventsource', 'status', 'esc_period', 'pause_suppressed']; 44 45 /** 46 * This property, if filled out, will contain all hostrgroup ids 47 * that requested scripts did inherit from. 48 * Keyed by scriptid. 49 * 50 * @var array|HostGroup[] 51 */ 52 protected $parent_host_groups = []; 53 54 /** 55 * @param array $options 56 * 57 * @throws APIException if the input is invalid. 58 * 59 * @return array|int 60 */ 61 public function get(array $options) { 62 $script_fields = ['scriptid', 'name', 'command', 'host_access', 'usrgrpid', 'groupid', 'description', 63 'confirmation', 'type', 'execute_on', 'timeout', 'parameters', 'scope', 'port', 'authtype', 'username', 64 'password', 'publickey', 'privatekey', 'menu_path' 65 ]; 66 $group_fields = ['groupid', 'name', 'flags', 'internal']; 67 $host_fields = ['hostid', 'host', 'name', 'description', 'status', 'proxy_hostid', 'inventory_mode', 'flags', 68 'ipmi_authtype', 'ipmi_privilege', 'ipmi_username', 'ipmi_password', 'maintenanceid', 'maintenance_status', 69 'maintenance_type', 'maintenance_from', 'tls_connect', 'tls_accept', 'tls_issuer', 'tls_subject' 70 ]; 71 72 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 73 // filter 74 'scriptids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 75 'hostids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 76 'groupids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 77 'usrgrpids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 78 'filter' => ['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [ 79 'scriptid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 80 'name' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 81 'command' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 82 'host_access' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', [PERM_READ, PERM_READ_WRITE])], 83 'usrgrpid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 84 'groupid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 85 'confirmation' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 86 'type' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', [ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT, ZBX_SCRIPT_TYPE_IPMI, ZBX_SCRIPT_TYPE_SSH, ZBX_SCRIPT_TYPE_TELNET, ZBX_SCRIPT_TYPE_WEBHOOK])], 87 'execute_on' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', [ZBX_SCRIPT_EXECUTE_ON_AGENT, ZBX_SCRIPT_EXECUTE_ON_SERVER, ZBX_SCRIPT_EXECUTE_ON_PROXY])], 88 'scope' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', [ZBX_SCRIPT_SCOPE_ACTION, ZBX_SCRIPT_SCOPE_HOST, ZBX_SCRIPT_SCOPE_EVENT])], 89 'menu_path' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 90 ]], 91 'search' => ['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [ 92 'name' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 93 'command' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 94 'description' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 95 'confirmation' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 96 'username' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 97 'menu_path' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 98 ]], 99 'searchByAny' => ['type' => API_BOOLEAN, 'default' => false], 100 'startSearch' => ['type' => API_FLAG, 'default' => false], 101 'excludeSearch' => ['type' => API_FLAG, 'default' => false], 102 'searchWildcardsEnabled' => ['type' => API_BOOLEAN, 'default' => false], 103 // output 104 'output' => ['type' => API_OUTPUT, 'in' => implode(',', $script_fields), 'default' => API_OUTPUT_EXTEND], 105 'selectGroups' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $group_fields), 'default' => null], 106 'selectHosts' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $host_fields), 'default' => null], 107 'selectActions' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $this->action_fields), 'default' => null], 108 'countOutput' => ['type' => API_FLAG, 'default' => false], 109 // sort and limit 110 'sortfield' => ['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []], 111 'sortorder' => ['type' => API_SORTORDER, 'default' => []], 112 'limit' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null], 113 // flags 114 'editable' => ['type' => API_BOOLEAN, 'default' => false], 115 'preservekeys' => ['type' => API_BOOLEAN, 'default' => false] 116 ]]; 117 if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) { 118 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 119 } 120 121 $sql_parts = [ 122 'select' => ['scripts' => 's.scriptid'], 123 'from' => ['scripts' => 'scripts s'], 124 'where' => [], 125 'order' => [] 126 ]; 127 128 // editable + permission check 129 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 130 if ($options['editable']) { 131 return $options['countOutput'] ? 0 : []; 132 } 133 134 $user_groups = getUserGroupsByUserId(self::$userData['userid']); 135 136 $sql_parts['where'][] = '(s.usrgrpid IS NULL OR '.dbConditionInt('s.usrgrpid', $user_groups).')'; 137 $sql_parts['where'][] = '(s.groupid IS NULL OR EXISTS ('. 138 'SELECT NULL'. 139 ' FROM rights r'. 140 ' WHERE s.groupid=r.id'. 141 ' AND '.dbConditionInt('r.groupid', $user_groups). 142 ' GROUP BY r.id'. 143 ' HAVING MIN(r.permission)>'.PERM_DENY. 144 '))'; 145 } 146 147 $host_groups = null; 148 $host_groups_by_hostids = null; 149 $host_groups_by_groupids = null; 150 151 // Hostids and groupids selection API calls must be made separately because we must intersect enriched groupids. 152 if ($options['hostids'] !== null) { 153 $host_groups_by_hostids = enrichParentGroups(API::HostGroup()->get([ 154 'output' => ['groupid', 'name'], 155 'hostids' => $options['hostids'], 156 'preservekeys' => true 157 ])); 158 } 159 if ($options['groupids'] !== null) { 160 $host_groups_by_groupids = enrichParentGroups(API::HostGroup()->get([ 161 'output' => ['groupid', 'name'], 162 'groupids' => $options['groupids'], 163 'preservekeys' => true 164 ])); 165 } 166 167 if ($host_groups_by_groupids !== null && $host_groups_by_hostids !== null) { 168 $host_groups = array_intersect_key($host_groups_by_hostids, $host_groups_by_groupids); 169 } 170 elseif ($host_groups_by_hostids !== null) { 171 $host_groups = $host_groups_by_hostids; 172 } 173 elseif ($host_groups_by_groupids !== null) { 174 $host_groups = $host_groups_by_groupids; 175 } 176 177 if ($host_groups !== null) { 178 $sql_parts['where'][] = '('.dbConditionInt('s.groupid', array_keys($host_groups)).' OR s.groupid IS NULL)'; 179 $this->parent_host_groups = $host_groups; 180 } 181 182 // usrgrpids 183 if ($options['usrgrpids'] !== null) { 184 $sql_parts['where'][] = '(s.usrgrpid IS NULL OR '.dbConditionInt('s.usrgrpid', $options['usrgrpids']).')'; 185 } 186 187 // scriptids 188 if ($options['scriptids'] !== null) { 189 $sql_parts['where'][] = dbConditionInt('s.scriptid', $options['scriptids']); 190 } 191 192 // search 193 if ($options['search'] !== null) { 194 zbx_db_search('scripts s', $options, $sql_parts); 195 } 196 197 // filter 198 if ($options['filter'] !== null) { 199 $this->dbFilter('scripts s', $options, $sql_parts); 200 } 201 202 $db_scripts = []; 203 204 $sql_parts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts); 205 $sql_parts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts); 206 207 $result = DBselect(self::createSelectQueryFromParts($sql_parts), $options['limit']); 208 209 while ($db_script = DBfetch($result)) { 210 if ($options['countOutput']) { 211 return $db_script['rowscount']; 212 } 213 214 $db_scripts[$db_script['scriptid']] = $db_script; 215 } 216 217 if ($db_scripts) { 218 $db_scripts = $this->addRelatedObjects($options, $db_scripts); 219 $db_scripts = $this->unsetExtraFields($db_scripts, ['scriptid', 'groupid', 'host_access'], 220 $options['output'] 221 ); 222 223 if (!$options['preservekeys']) { 224 $db_scripts = array_values($db_scripts); 225 } 226 } 227 228 return $db_scripts; 229 } 230 231 /** 232 * @param array $scripts 233 * 234 * @return array 235 */ 236 public function create(array $scripts) { 237 $this->validateCreate($scripts); 238 239 $scriptids = DB::insert('scripts', $scripts); 240 $scripts_params = []; 241 242 foreach ($scripts as $index => &$script) { 243 $script['scriptid'] = $scriptids[$index]; 244 245 if ($script['type'] == ZBX_SCRIPT_TYPE_WEBHOOK && array_key_exists('parameters', $script)) { 246 foreach ($script['parameters'] as $param) { 247 $scripts_params[] = ['scriptid' => $script['scriptid']] + $param; 248 } 249 } 250 } 251 unset($script); 252 253 if ($scripts_params) { 254 DB::insertBatch('script_param', $scripts_params); 255 } 256 257 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_SCRIPT, $scripts); 258 259 return ['scriptids' => $scriptids]; 260 } 261 262 /** 263 * @param array $scripts 264 * 265 * @throws APIException if the input is invalid 266 */ 267 protected function validateCreate(array &$scripts) { 268 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 269 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 270 } 271 272 /* 273 * Get general validation rules and firstly validate name uniqueness and all the possible fields, so that there 274 * are no invalid fields for any of the script types. Unfortunaly there is also a drawback, since field types 275 * validated before we know what rules belong to each script type. 276 */ 277 $api_input_rules = $this->getValidationRules('create', $common_fields); 278 279 if (!CApiInputValidator::validate($api_input_rules, $scripts, '/', $error)) { 280 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 281 } 282 283 /* 284 * Then validate each script separately. Depending on script type, each script may have different set of allowed 285 * fields. Then in case the type is SSH and authtype is set, validate parameters again. 286 */ 287 $i = 0; 288 $check_names = []; 289 290 foreach ($scripts as $script) { 291 $path = '/'.++$i; 292 293 $type_rules = $this->getTypeValidationRules($script['type'], 'create', $type_fields); 294 $this->getScopeValidationRules($script['scope'], $scope_fields); 295 296 $type_rules['fields'] += $common_fields + $scope_fields; 297 298 if (!CApiInputValidator::validate($type_rules, $script, $path, $error)) { 299 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 300 } 301 302 if (array_key_exists('authtype', $script)) { 303 $ssh_rules = $this->getAuthTypeValidationRules($script['authtype'], 'create'); 304 $ssh_rules['fields'] += $common_fields + $type_fields + $scope_fields; 305 306 if (!CApiInputValidator::validate($ssh_rules, $script, $path, $error)) { 307 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 308 } 309 } 310 311 $check_names[$script['name']] = true; 312 } 313 314 $db_script_names = API::getApiService()->select('scripts', [ 315 'output' => ['scriptid'], 316 'filter' => ['name' => array_keys($check_names)] 317 ]); 318 319 if ($db_script_names) { 320 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Script "%1$s" already exists.', $script['name'])); 321 } 322 323 // Finally check User and Host IDs. 324 $this->checkUserGroups($scripts); 325 $this->checkHostGroups($scripts); 326 } 327 328 /** 329 * @param array $scripts 330 * 331 * @return array 332 */ 333 public function update(array $scripts) { 334 $this->validateUpdate($scripts, $db_scripts); 335 336 $upd_scripts = []; 337 $scripts_params = []; 338 339 foreach ($scripts as $script) { 340 $scriptid = $script['scriptid']; 341 $db_script = $db_scripts[$scriptid]; 342 $db_type = $db_script['type']; 343 $db_authtype = $db_script['authtype']; 344 $db_scope = $db_script['scope']; 345 $type = array_key_exists('type', $script) ? $script['type'] : $db_type; 346 $authtype = array_key_exists('authtype', $script) ? $script['authtype'] : $db_authtype; 347 $scope = array_key_exists('scope', $script) ? $script['scope'] : $db_scope; 348 349 $upd_script = []; 350 351 // strings 352 foreach (['name', 'command', 'description', 'confirmation', 'timeout', 'menu_path', 'username', 'publickey', 353 'privatekey', 'password'] as $field_name) { 354 if (array_key_exists($field_name, $script) && $script[$field_name] !== $db_script[$field_name]) { 355 $upd_script[$field_name] = $script[$field_name]; 356 } 357 } 358 359 // integers 360 foreach (['type', 'execute_on', 'usrgrpid', 'groupid', 'host_access', 'scope', 'port', 'authtype'] 361 as $field_name) { 362 if (array_key_exists($field_name, $script) && $script[$field_name] != $db_script[$field_name]) { 363 $upd_script[$field_name] = $script[$field_name]; 364 } 365 } 366 367 // No mattter what the old type was, clear and reset all unnecessary fields from any other types. 368 if ($type != $db_type) { 369 switch ($type) { 370 case ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT: 371 $upd_script['port'] = ''; 372 $upd_script['authtype'] = DB::getDefault('scripts', 'authtype'); 373 $upd_script['username'] = ''; 374 $upd_script['password'] = ''; 375 $upd_script['publickey'] = ''; 376 $upd_script['privatekey'] = ''; 377 break; 378 379 case ZBX_SCRIPT_TYPE_IPMI: 380 $upd_script['port'] = ''; 381 $upd_script['authtype'] = DB::getDefault('scripts', 'authtype'); 382 $upd_script['username'] = ''; 383 $upd_script['password'] = ''; 384 $upd_script['publickey'] = ''; 385 $upd_script['privatekey'] = ''; 386 $upd_script['execute_on'] = DB::getDefault('scripts', 'execute_on'); 387 break; 388 389 case ZBX_SCRIPT_TYPE_SSH: 390 $upd_script['execute_on'] = DB::getDefault('scripts', 'execute_on'); 391 break; 392 393 case ZBX_SCRIPT_TYPE_TELNET: 394 $upd_script['authtype'] = DB::getDefault('scripts', 'authtype'); 395 $upd_script['publickey'] = ''; 396 $upd_script['privatekey'] = ''; 397 $upd_script['execute_on'] = DB::getDefault('scripts', 'execute_on'); 398 break; 399 400 case ZBX_SCRIPT_TYPE_WEBHOOK: 401 $upd_script['port'] = ''; 402 $upd_script['authtype'] = DB::getDefault('scripts', 'authtype'); 403 $upd_script['username'] = ''; 404 $upd_script['password'] = ''; 405 $upd_script['publickey'] = ''; 406 $upd_script['privatekey'] = ''; 407 $upd_script['execute_on'] = DB::getDefault('scripts', 'execute_on'); 408 break; 409 } 410 } 411 elseif ($type == ZBX_SCRIPT_TYPE_SSH && $authtype != $db_authtype && $authtype == ITEM_AUTHTYPE_PASSWORD) { 412 $upd_script['publickey'] = ''; 413 $upd_script['privatekey'] = ''; 414 } 415 416 if ($scope != $db_scope && $scope == ZBX_SCRIPT_SCOPE_ACTION) { 417 $upd_script['menu_path'] = ''; 418 $upd_script['usrgrpid'] = 0; 419 $upd_script['host_access'] = DB::getDefault('scripts', 'host_access');; 420 $upd_script['confirmation'] = ''; 421 } 422 423 if ($type == ZBX_SCRIPT_TYPE_WEBHOOK && array_key_exists('parameters', $script)) { 424 $params = []; 425 426 foreach ($script['parameters'] as $param) { 427 $params[$param['name']] = $param['value']; 428 } 429 430 $scripts_params[$scriptid] = $params; 431 unset($script['parameters']); 432 } 433 434 if ($type != $db_type && $db_type == ZBX_SCRIPT_TYPE_WEBHOOK) { 435 $upd_script['timeout'] = DB::getDefault('scripts', 'timeout'); 436 $scripts_params[$scriptid] = []; 437 } 438 439 if ($upd_script) { 440 $upd_scripts[] = [ 441 'values' => $upd_script, 442 'where' => ['scriptid' => $scriptid] 443 ]; 444 } 445 } 446 447 if ($upd_scripts) { 448 DB::update('scripts', $upd_scripts); 449 } 450 451 if ($scripts_params) { 452 $insert_script_param = []; 453 $delete_script_param = []; 454 $update_script_param = []; 455 $db_scripts_params = DB::select('script_param', [ 456 'output' => ['script_paramid', 'scriptid', 'name', 'value'], 457 'filter' => ['scriptid' => array_keys($scripts_params)] 458 ]); 459 460 foreach ($db_scripts_params as $param) { 461 $scriptid = $param['scriptid']; 462 463 if (!array_key_exists($param['name'], $scripts_params[$scriptid])) { 464 $delete_script_param[] = $param['script_paramid']; 465 } 466 elseif ($scripts_params[$scriptid][$param['name']] !== $param['value']) { 467 $update_script_param[] = [ 468 'values' => ['value' => $scripts_params[$scriptid][$param['name']]], 469 'where' => ['script_paramid' => $param['script_paramid']] 470 ]; 471 unset($scripts_params[$scriptid][$param['name']]); 472 } 473 else { 474 unset($scripts_params[$scriptid][$param['name']]); 475 } 476 } 477 478 $scripts_params = array_filter($scripts_params); 479 480 foreach ($scripts_params as $scriptid => $params) { 481 foreach ($params as $name => $value) { 482 $insert_script_param[] = compact('scriptid', 'name', 'value'); 483 } 484 } 485 486 if ($delete_script_param) { 487 DB::delete('script_param', ['script_paramid' => array_keys(array_flip($delete_script_param))]); 488 } 489 490 if ($update_script_param) { 491 DB::update('script_param', $update_script_param); 492 } 493 494 if ($insert_script_param) { 495 DB::insert('script_param', $insert_script_param); 496 } 497 } 498 499 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_SCRIPT, $scripts, $db_scripts); 500 501 return ['scriptids' => zbx_objectValues($scripts, 'scriptid')]; 502 } 503 504 /** 505 * @param array $scripts 506 * @param array $db_scripts 507 * 508 * @throws APIException if the input is invalid 509 */ 510 protected function validateUpdate(array &$scripts, array &$db_scripts = null) { 511 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 512 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 513 } 514 515 /* 516 * Get general validation rules and firstly validate name uniqueness and all the possible fields, so that there 517 * are no invalid fields for any of the script types. Unfortunaly there is also a drawback, since field types 518 * validated before we know what rules belong to each script type. 519 */ 520 $api_input_rules = $this->getValidationRules('update', $common_fields); 521 522 if (!CApiInputValidator::validate($api_input_rules, $scripts, '/', $error)) { 523 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 524 } 525 526 // Continue to validate script name. 527 $db_scripts = DB::select('scripts', [ 528 'output' => ['scriptid', 'name', 'command', 'host_access', 'usrgrpid', 'groupid', 'description', 529 'confirmation', 'type', 'execute_on', 'timeout', 'scope', 'port', 'authtype', 'username', 'password', 530 'publickey', 'privatekey', 'menu_path' 531 ], 532 'scriptids' => zbx_objectValues($scripts, 'scriptid'), 533 'preservekeys' => true 534 ]); 535 536 $check_names = []; 537 foreach ($scripts as $script) { 538 if (!array_key_exists($script['scriptid'], $db_scripts)) { 539 self::exception(ZBX_API_ERROR_PERMISSIONS, 540 _('No permissions to referred object or it does not exist!') 541 ); 542 } 543 544 if (array_key_exists('name', $script)) { 545 $check_names[$script['name']] = true; 546 } 547 } 548 549 if ($check_names) { 550 $db_script_names = API::getApiService()->select('scripts', [ 551 'output' => ['scriptid', 'name'], 552 'filter' => ['name' => array_keys($check_names)] 553 ]); 554 $db_script_names = zbx_toHash($db_script_names, 'name'); 555 556 foreach ($scripts as $script) { 557 if (array_key_exists('name', $script) 558 && array_key_exists($script['name'], $db_script_names) 559 && !idcmp($db_script_names[$script['name']]['scriptid'], $script['scriptid'])) { 560 self::exception(ZBX_API_ERROR_PARAMETERS, 561 _s('Script "%1$s" already exists.', $script['name']) 562 ); 563 } 564 } 565 } 566 567 // Validate if scripts belong to actions and scope can be changed. 568 $action_scriptids = []; 569 570 foreach ($scripts as $script) { 571 $db_script = $db_scripts[$script['scriptid']]; 572 573 if (array_key_exists('scope', $script) && $script['scope'] != ZBX_SCRIPT_SCOPE_ACTION 574 && $db_script['scope'] == ZBX_SCRIPT_SCOPE_ACTION) { 575 $action_scriptids[$script['scriptid']] = true; 576 } 577 } 578 579 if ($action_scriptids) { 580 $actions = API::Action()->get([ 581 'output' => ['actionid', 'name'], 582 'scriptids' => array_keys($action_scriptids), 583 'selectOperations' => ['opcommand'], 584 'selectRecoveryOperations' => ['opcommand'], 585 'selectAcknowledgeOperations' => ['opcommand'] 586 ]); 587 588 if ($actions) { 589 foreach ($scripts as $script) { 590 $db_script = $db_scripts[$script['scriptid']]; 591 592 if (array_key_exists('scope', $script) && $script['scope'] != ZBX_SCRIPT_SCOPE_ACTION 593 && $db_script['scope'] == ZBX_SCRIPT_SCOPE_ACTION) { 594 foreach ($actions as $action) { 595 if ($action['operations']) { 596 // Find at least one usage of script in any of operations. 597 foreach ($action['operations'] as $operation) { 598 if (array_key_exists('opcommand', $operation) 599 && bccomp($operation['opcommand']['scriptid'], $script['scriptid']) == 0) { 600 601 self::exception(ZBX_API_ERROR_PARAMETERS, 602 _s('Cannot update script scope. Script "%1$s" is used in action "%2$s".', 603 $db_script['name'], $action['name'] 604 ) 605 ); 606 } 607 } 608 } 609 610 if ($action['recoveryOperations']) { 611 foreach ($action['recoveryOperations'] as $operation) { 612 if (array_key_exists('opcommand', $operation) 613 && bccomp($operation['opcommand']['scriptid'], $script['scriptid']) == 0) { 614 self::exception(ZBX_API_ERROR_PARAMETERS, 615 _s('Cannot update script scope. Script "%1$s" is used in action "%2$s".', 616 $db_script['name'], $action['name'] 617 ) 618 ); 619 } 620 } 621 } 622 623 if ($action['acknowledgeOperations']) { 624 foreach ($action['acknowledgeOperations'] as $operation) { 625 if (array_key_exists('opcommand', $operation) 626 && bccomp($operation['opcommand']['scriptid'], $script['scriptid']) == 0) { 627 self::exception(ZBX_API_ERROR_PARAMETERS, 628 _s('Cannot update script scope. Script "%1$s" is used in action "%2$s".', 629 $db_script['name'], $action['name'] 630 ) 631 ); 632 } 633 } 634 } 635 } 636 } 637 } 638 } 639 } 640 641 // Populate common and mandatory fields. 642 $scripts = zbx_toHash($scripts, 'scriptid'); 643 $scripts = $this->extendFromObjects($scripts, $db_scripts, ['name', 'type', 'command', 'scope']); 644 645 $i = 0; 646 foreach ($scripts as &$script) { 647 $path = '/'.++$i; 648 $db_script = $db_scripts[$script['scriptid']]; 649 $method = 'update'; 650 651 if (array_key_exists('type', $script) && $script['type'] != $db_script['type']) { 652 // This means that all other fields are now required just like create method. 653 $method = 'create'; 654 655 // Populate username field, if no new name is given and types are similar to previous. 656 if (!array_key_exists('username', $script) 657 && (($db_script['type'] == ZBX_SCRIPT_TYPE_TELNET && $script['type'] == ZBX_SCRIPT_TYPE_SSH) 658 || ($db_script['type'] == ZBX_SCRIPT_TYPE_SSH 659 && $script['type'] == ZBX_SCRIPT_TYPE_TELNET))) { 660 $script['username'] = $db_script['username']; 661 } 662 } 663 664 $type_rules = $this->getTypeValidationRules($script['type'], $method, $type_fields); 665 $this->getScopeValidationRules($script['scope'], $scope_fields); 666 667 $type_rules['fields'] += $common_fields + $scope_fields; 668 669 if (!CApiInputValidator::validate($type_rules, $script, $path, $error)) { 670 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 671 } 672 673 if ($script['type'] == ZBX_SCRIPT_TYPE_SSH) { 674 $method = 'update'; 675 676 if (array_key_exists('authtype', $script) && $script['authtype'] != $db_script['authtype']) { 677 $method = 'create'; 678 } 679 680 $script = $this->extendFromObjects([$script], [$db_script], ['authtype'])[0]; 681 682 $ssh_rules = $this->getAuthTypeValidationRules($script['authtype'], $method); 683 $ssh_rules['fields'] += $common_fields + $type_fields + $scope_fields; 684 685 if (!CApiInputValidator::validate($ssh_rules, $script, $path, $error)) { 686 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 687 } 688 } 689 } 690 unset($script); 691 692 $this->checkUserGroups($scripts); 693 $this->checkHostGroups($scripts); 694 } 695 696 /** 697 * Get general validation rules. 698 * 699 * @param string $method [IN] API method "create" or "update". 700 * @param array $common_fields [OUT] Returns common fields for all script types. 701 * 702 * @return array 703 */ 704 protected function getValidationRules(string $method, &$common_fields = []): array { 705 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'fields' => []]; 706 707 $common_fields = [ 708 'name' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('scripts', 'name')], 709 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT, ZBX_SCRIPT_TYPE_IPMI, ZBX_SCRIPT_TYPE_SSH, ZBX_SCRIPT_TYPE_TELNET, ZBX_SCRIPT_TYPE_WEBHOOK])], 710 'scope' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SCRIPT_SCOPE_ACTION, ZBX_SCRIPT_SCOPE_HOST, ZBX_SCRIPT_SCOPE_EVENT])], 711 'command' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('scripts', 'command')], 712 'groupid' => ['type' => API_ID], 713 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('scripts', 'description')] 714 ]; 715 716 if ($method === 'create') { 717 $common_fields['scope']['default'] = ZBX_SCRIPT_SCOPE_ACTION; 718 $api_input_rules['uniq'] = [['name']]; 719 $common_fields['name']['flags'] |= API_REQUIRED; 720 $common_fields['type']['flags'] = API_REQUIRED; 721 $common_fields['command']['flags'] |= API_REQUIRED; 722 } 723 else { 724 $api_input_rules['uniq'] = [['scriptid'], ['name']]; 725 $common_fields += ['scriptid' => ['type' => API_ID, 'flags' => API_REQUIRED]]; 726 } 727 728 /* 729 * Merge together optional fields that depend on script type. Some of these fields are not required for some 730 * script types. Set only type for now. Unique parameter names, lengths and other flags are set later. 731 */ 732 $api_input_rules['fields'] += $common_fields + [ 733 'execute_on' => ['type' => API_INT32], 734 'menu_path' => ['type' => API_STRING_UTF8], 735 'usrgrpid' => ['type' => API_ID], 736 'host_access' => ['type' => API_INT32], 737 'confirmation' => ['type' => API_STRING_UTF8], 738 'port' => ['type' => API_PORT, 'flags' => API_ALLOW_USER_MACRO], 739 'authtype' => ['type' => API_INT32], 740 'username' => ['type' => API_STRING_UTF8], 741 'publickey' => ['type' => API_STRING_UTF8], 742 'privatekey' => ['type' => API_STRING_UTF8], 743 'password' => ['type' => API_STRING_UTF8], 744 'timeout' => ['type' => API_TIME_UNIT], 745 'parameters' => ['type' => API_OBJECTS, 'fields' => [ 746 'name' => ['type' => API_STRING_UTF8], 747 'value' => ['type' => API_STRING_UTF8] 748 ]] 749 ]; 750 751 return $api_input_rules; 752 } 753 754 /** 755 * Get validation rules for script scope. 756 * 757 * @param int $scope [IN] Script scope. 758 * @param array $common_fields [OUT] Returns common fields for specific script scope. 759 * 760 * @return array 761 */ 762 protected function getScopeValidationRules(int $scope, &$common_fields = []): array { 763 $api_input_rules = ['type' => API_OBJECT, 'fields' => []]; 764 $common_fields = []; 765 766 if ($scope == ZBX_SCRIPT_SCOPE_HOST || $scope == ZBX_SCRIPT_SCOPE_EVENT) { 767 $common_fields = [ 768 'menu_path' => ['type' => API_SCRIPT_MENU_PATH, 'length' => DB::getFieldLength('scripts', 'menu_path')], 769 'usrgrpid' => ['type' => API_ID], 770 'host_access' => ['type' => API_INT32, 'in' => implode(',', [PERM_READ, PERM_READ_WRITE])], 771 'confirmation' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('scripts', 'confirmation')] 772 ]; 773 774 $api_input_rules['fields'] += $common_fields; 775 } 776 777 return $api_input_rules; 778 } 779 780 /** 781 * Get validation rules for each script type. 782 * 783 * @param int $type [IN] Script type. 784 * @param string $method [IN] API method "create" or "update". 785 * @param array $common_fields [OUT] Returns common fields for specific script type. 786 * 787 * @return array 788 */ 789 protected function getTypeValidationRules(int $type, string $method, &$common_fields = []): array { 790 $api_input_rules = ['type' => API_OBJECT, 'fields' => []]; 791 $common_fields = []; 792 793 switch ($type) { 794 case ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT: 795 $api_input_rules['fields'] += [ 796 'execute_on' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SCRIPT_EXECUTE_ON_AGENT, ZBX_SCRIPT_EXECUTE_ON_SERVER, ZBX_SCRIPT_EXECUTE_ON_PROXY])] 797 ]; 798 break; 799 800 case ZBX_SCRIPT_TYPE_SSH: 801 $common_fields = [ 802 'port' => ['type' => API_PORT, 'flags' => API_ALLOW_USER_MACRO], 803 'authtype' => ['type' => API_INT32, 'in' => implode(',', [ITEM_AUTHTYPE_PASSWORD, ITEM_AUTHTYPE_PUBLICKEY])], 804 'username' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('scripts', 'username')], 805 'password' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('scripts', 'password')] 806 ]; 807 808 if ($method === 'create') { 809 $common_fields['username']['flags'] |= API_REQUIRED; 810 } 811 812 $api_input_rules['fields'] += $common_fields + [ 813 'publickey' => ['type' => API_STRING_UTF8], 814 'privatekey' => ['type' => API_STRING_UTF8] 815 ]; 816 break; 817 818 case ZBX_SCRIPT_TYPE_TELNET: 819 $api_input_rules['fields'] += [ 820 'port' => ['type' => API_PORT, 'flags' => API_ALLOW_USER_MACRO], 821 'username' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('scripts', 'username')], 822 'password' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('scripts', 'password')] 823 ]; 824 825 if ($method === 'create') { 826 $api_input_rules['fields']['username']['flags'] |= API_REQUIRED; 827 } 828 break; 829 830 case ZBX_SCRIPT_TYPE_WEBHOOK: 831 $api_input_rules['fields'] += [ 832 'timeout' => ['type' => API_TIME_UNIT, 'in' => '1:'.SEC_PER_MIN], 833 'parameters' => ['type' => API_OBJECTS, 'uniq' => [['name']], 'fields' => [ 834 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('script_param', 'name')], 835 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('script_param', 'value')] 836 ]] 837 ]; 838 break; 839 } 840 841 return $api_input_rules; 842 } 843 844 /** 845 * Get validation rules for each script authtype. 846 * 847 * @param int $authtype Script authtype. 848 * @param string $method API method "create" or "update". 849 * 850 * @return array 851 */ 852 protected function getAuthTypeValidationRules(int $authtype, string $method): array { 853 $api_input_rules = ['type' => API_OBJECT, 'fields' => []]; 854 855 if ($authtype == ITEM_AUTHTYPE_PUBLICKEY) { 856 $api_input_rules['fields'] += [ 857 'publickey' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('scripts', 'publickey')], 858 'privatekey' => ['type' => API_STRING_UTF8,'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('scripts', 'privatekey')] 859 ]; 860 861 if ($method === 'create') { 862 $api_input_rules['fields']['publickey']['flags'] |= API_REQUIRED; 863 $api_input_rules['fields']['privatekey']['flags'] |= API_REQUIRED; 864 } 865 } 866 867 return $api_input_rules; 868 } 869 870 /** 871 * Check for valid user groups. 872 * 873 * @param array $scripts 874 * @param array $scripts[]['usrgrpid'] (optional) 875 * 876 * @throws APIException if user group is not exists. 877 */ 878 private function checkUserGroups(array $scripts) { 879 $usrgrpids = []; 880 881 foreach ($scripts as $script) { 882 if (array_key_exists('usrgrpid', $script) && $script['usrgrpid'] != 0) { 883 $usrgrpids[$script['usrgrpid']] = true; 884 } 885 } 886 887 if (!$usrgrpids) { 888 return; 889 } 890 891 $usrgrpids = array_keys($usrgrpids); 892 893 $db_usrgrps = DB::select('usrgrp', [ 894 'output' => [], 895 'usrgrpids' => $usrgrpids, 896 'preservekeys' => true 897 ]); 898 899 foreach ($usrgrpids as $usrgrpid) { 900 if (!array_key_exists($usrgrpid, $db_usrgrps)) { 901 self::exception(ZBX_API_ERROR_PARAMETERS, _s('User group with ID "%1$s" is not available.', $usrgrpid)); 902 } 903 } 904 } 905 906 /** 907 * Check for valid host groups. 908 * 909 * @param array $scripts 910 * @param array $scripts[]['groupid'] (optional) 911 * 912 * @throws APIException if host group is not exists. 913 */ 914 private function checkHostGroups(array $scripts) { 915 $groupids = []; 916 917 foreach ($scripts as $script) { 918 if (array_key_exists('groupid', $script) && $script['groupid'] != 0) { 919 $groupids[$script['groupid']] = true; 920 } 921 } 922 923 if (!$groupids) { 924 return; 925 } 926 927 $groupids = array_keys($groupids); 928 929 $db_groups = DB::select('hstgrp', [ 930 'output' => [], 931 'groupids' => $groupids, 932 'preservekeys' => true 933 ]); 934 935 foreach ($groupids as $groupid) { 936 if (!array_key_exists($groupid, $db_groups)) { 937 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host group with ID "%1$s" is not available.', $groupid)); 938 } 939 } 940 } 941 942 /** 943 * @param array $scriptids 944 * 945 * @return array 946 */ 947 public function delete(array $scriptids) { 948 $this->validateDelete($scriptids, $db_scripts); 949 950 DB::delete('scripts', ['scriptid' => $scriptids]); 951 952 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_SCRIPT, $db_scripts); 953 954 return ['scriptids' => $scriptids]; 955 } 956 957 /** 958 * @param array $scriptids 959 * @param array $db_scripts 960 * 961 * @throws APIException if the input is invalid 962 */ 963 protected function validateDelete(array &$scriptids, array &$db_scripts = null) { 964 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 965 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 966 } 967 968 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 969 if (!CApiInputValidator::validate($api_input_rules, $scriptids, '/', $error)) { 970 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 971 } 972 973 $db_scripts = DB::select('scripts', [ 974 'output' => ['scriptid', 'name'], 975 'scriptids' => $scriptids, 976 'preservekeys' => true 977 ]); 978 979 foreach ($scriptids as $scriptid) { 980 if (!array_key_exists($scriptid, $db_scripts)) { 981 self::exception(ZBX_API_ERROR_PERMISSIONS, 982 _('No permissions to referred object or it does not exist!') 983 ); 984 } 985 } 986 987 // Check if deleted scripts used in actions. 988 $db_actions = DBselect( 989 'SELECT a.name,oc.scriptid'. 990 ' FROM opcommand oc,operations o,actions a'. 991 ' WHERE oc.operationid=o.operationid'. 992 ' AND o.actionid=a.actionid'. 993 ' AND '.dbConditionInt('oc.scriptid', $scriptids), 994 1 995 ); 996 997 if ($db_action = DBfetch($db_actions)) { 998 self::exception(ZBX_API_ERROR_PARAMETERS, 999 _s('Cannot delete scripts. Script "%1$s" is used in action operation "%2$s".', 1000 $db_scripts[$db_action['scriptid']]['name'], $db_action['name'] 1001 ) 1002 ); 1003 } 1004 } 1005 1006 /** 1007 * @param array $data 1008 * 1009 * @return array 1010 */ 1011 public function execute(array $data) { 1012 global $ZBX_SERVER, $ZBX_SERVER_PORT; 1013 1014 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 1015 'scriptid' => ['type' => API_ID, 'flags' => API_REQUIRED], 1016 'hostid' => ['type' => API_ID], 1017 'eventid' => ['type' => API_ID] 1018 ]]; 1019 if (!CApiInputValidator::validate($api_input_rules, $data, '/', $error)) { 1020 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 1021 } 1022 1023 if (!array_key_exists('hostid', $data) && !array_key_exists('eventid', $data)) { 1024 self::exception(ZBX_API_ERROR_PARAMETERS, 1025 _s('Invalid parameter "%1$s": %2$s.', '/', _s('the parameter "%1$s" is missing', 'eventid')) 1026 ); 1027 } 1028 1029 if (array_key_exists('hostid', $data) && array_key_exists('eventid', $data)) { 1030 self::exception(ZBX_API_ERROR_PARAMETERS, 1031 _s('Invalid parameter "%1$s": %2$s.', '/', _s('unexpected parameter "%1$s"', 'eventid')) 1032 ); 1033 } 1034 1035 if (array_key_exists('eventid', $data)) { 1036 $db_events = API::Event()->get([ 1037 'output' => [], 1038 'selectHosts' => ['hostid'], 1039 'eventids' => $data['eventid'] 1040 ]); 1041 if (!$db_events) { 1042 self::exception(ZBX_API_ERROR_PERMISSIONS, 1043 _('No permissions to referred object or it does not exist!') 1044 ); 1045 } 1046 1047 $hostids = array_column($db_events[0]['hosts'], 'hostid'); 1048 $is_event = true; 1049 } 1050 else { 1051 $hostids = $data['hostid']; 1052 $is_event = false; 1053 1054 $db_hosts = API::Host()->get([ 1055 'output' => [], 1056 'hostids' => $hostids 1057 ]); 1058 if (!$db_hosts) { 1059 self::exception(ZBX_API_ERROR_PERMISSIONS, 1060 _('No permissions to referred object or it does not exist!') 1061 ); 1062 } 1063 } 1064 1065 $db_scripts = $this->get([ 1066 'output' => [], 1067 'hostids' => $hostids, 1068 'scriptids' => $data['scriptid'] 1069 ]); 1070 if (!$db_scripts) { 1071 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 1072 } 1073 1074 // execute script 1075 $zabbix_server = new CZabbixServer($ZBX_SERVER, $ZBX_SERVER_PORT, 1076 timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::CONNECT_TIMEOUT)), 1077 timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::SCRIPT_TIMEOUT)), ZBX_SOCKET_BYTES_LIMIT 1078 ); 1079 $result = $zabbix_server->executeScript($data['scriptid'], self::$userData['sessionid'], 1080 $is_event ? null : $data['hostid'], 1081 $is_event ? $data['eventid'] : null 1082 ); 1083 1084 if ($result !== false) { 1085 // return the result in a backwards-compatible format 1086 return [ 1087 'response' => 'success', 1088 'value' => $result, 1089 'debug' => $zabbix_server->getDebug() 1090 ]; 1091 } 1092 else { 1093 self::exception(ZBX_API_ERROR_INTERNAL, $zabbix_server->getError()); 1094 } 1095 } 1096 1097 /** 1098 * Returns all the scripts that are available on each given host. 1099 * 1100 * @param $hostids 1101 * 1102 * @return array 1103 */ 1104 public function getScriptsByHosts($hostids) { 1105 zbx_value2array($hostids); 1106 1107 $scripts_by_host = []; 1108 1109 if (!$hostids) { 1110 return $scripts_by_host; 1111 } 1112 1113 foreach ($hostids as $hostid) { 1114 $scripts_by_host[$hostid] = []; 1115 } 1116 1117 $scripts = $this->get([ 1118 'output' => API_OUTPUT_EXTEND, 1119 'hostids' => $hostids, 1120 'sortfield' => 'name', 1121 'preservekeys' => true 1122 ]); 1123 1124 $scripts = $this->addRelatedGroupsAndHosts([ 1125 'selectGroups' => null, 1126 'selectHosts' => ['hostid'] 1127 ], $scripts, $hostids); 1128 1129 if ($scripts) { 1130 // resolve macros 1131 $macros_data = []; 1132 foreach ($scripts as $scriptid => $script) { 1133 if (!empty($script['confirmation'])) { 1134 foreach ($script['hosts'] as $host) { 1135 if (isset($scripts_by_host[$host['hostid']])) { 1136 $macros_data[$host['hostid']][$scriptid] = $script['confirmation']; 1137 } 1138 } 1139 } 1140 } 1141 if ($macros_data) { 1142 $macros_data = CMacrosResolverHelper::resolve([ 1143 'config' => 'scriptConfirmation', 1144 'data' => $macros_data 1145 ]); 1146 } 1147 1148 foreach ($scripts as $scriptid => $script) { 1149 $hosts = $script['hosts']; 1150 unset($script['hosts']); 1151 // set script to host 1152 foreach ($hosts as $host) { 1153 $hostid = $host['hostid']; 1154 1155 if (isset($scripts_by_host[$hostid])) { 1156 $size = count($scripts_by_host[$hostid]); 1157 $scripts_by_host[$hostid][$size] = $script; 1158 1159 // set confirmation text with resolved macros 1160 if (isset($macros_data[$hostid][$scriptid]) && $script['confirmation']) { 1161 $scripts_by_host[$hostid][$size]['confirmation'] = $macros_data[$hostid][$scriptid]; 1162 } 1163 } 1164 } 1165 } 1166 } 1167 1168 return $scripts_by_host; 1169 } 1170 1171 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 1172 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 1173 1174 if ($options['selectGroups'] !== null || $options['selectHosts'] !== null) { 1175 $sqlParts = $this->addQuerySelect($this->fieldId('groupid'), $sqlParts); 1176 $sqlParts = $this->addQuerySelect($this->fieldId('host_access'), $sqlParts); 1177 } 1178 1179 return $sqlParts; 1180 } 1181 1182 /** 1183 * Applies relational subselect onto already fetched result. 1184 * 1185 * @param array $options 1186 * @param array $result 1187 * 1188 * @return array $result 1189 */ 1190 protected function addRelatedObjects(array $options, array $result) { 1191 $result = parent::addRelatedObjects($options, $result); 1192 1193 // Adding actions. 1194 if ($options['selectActions'] !== null && $options['selectActions'] !== API_OUTPUT_COUNT) { 1195 foreach ($result as $scriptid => &$row) { 1196 $row['actions'] = []; 1197 } 1198 1199 $action_scriptids = []; 1200 1201 if ($this->outputIsRequested('scope', $options['output'])) { 1202 foreach ($result as $scriptid => $row) { 1203 if ($row['scope'] == ZBX_SCRIPT_SCOPE_ACTION) { 1204 $action_scriptids[] = $scriptid; 1205 } 1206 } 1207 } 1208 else { 1209 $db_scripts = API::getApiService()->select('scripts', [ 1210 'output' => ['scope'], 1211 'filter' => ['scriptid' => array_keys($result)], 1212 'preservekeys' => true 1213 ]); 1214 $db_scripts = $this->extendFromObjects($result, $db_scripts, ['scope']); 1215 1216 foreach ($db_scripts as $scriptid => $db_script) { 1217 if ($db_script['scope'] == ZBX_SCRIPT_SCOPE_ACTION) { 1218 $action_scriptids[] = $scriptid; 1219 } 1220 } 1221 } 1222 1223 if ($action_scriptids) { 1224 if ($options['selectActions'] === API_OUTPUT_EXTEND) { 1225 $action_fields = array_map(function ($field) { return 'a.'.$field; }, $this->action_fields); 1226 $action_fields = implode(',', $action_fields); 1227 } 1228 elseif (is_array($options['selectActions'])) { 1229 $action_fields = $options['selectActions']; 1230 1231 if (!in_array('actionid', $options['selectActions'])) { 1232 $action_fields[] = 'actionid'; 1233 } 1234 1235 $action_fields = array_map(function ($field) { return 'a.'.$field; }, $action_fields); 1236 $action_fields = implode(',', $action_fields); 1237 } 1238 1239 $db_script_actions = DBfetchArray(DBselect( 1240 'SELECT DISTINCT oc.scriptid,'.$action_fields. 1241 ' FROM actions a,operations o,opcommand oc'. 1242 ' WHERE a.actionid=o.actionid'. 1243 ' AND o.operationid=oc.operationid'. 1244 ' AND '.dbConditionInt('oc.scriptid', $action_scriptids) 1245 )); 1246 1247 foreach ($result as $scriptid => &$row) { 1248 if ($db_script_actions) { 1249 foreach ($db_script_actions as $db_script_action) { 1250 if (bccomp($db_script_action['scriptid'], $scriptid) == 0) { 1251 unset($db_script_action['scriptid']); 1252 $row['actions'][] = $db_script_action; 1253 } 1254 } 1255 1256 $row['actions'] = $this->unsetExtraFields($row['actions'], ['actionid'], 1257 $options['selectActions'] 1258 ); 1259 } 1260 } 1261 unset($row); 1262 } 1263 } 1264 1265 if ($this->outputIsRequested('parameters', $options['output'])) { 1266 foreach ($result as $scriptid => $script) { 1267 $result[$scriptid]['parameters'] = []; 1268 } 1269 1270 $parameters = DB::select('script_param', [ 1271 'output' => ['scriptid', 'name', 'value'], 1272 'filter' => ['scriptid' => array_keys($result)] 1273 ]); 1274 1275 foreach ($parameters as $parameter) { 1276 $result[$parameter['scriptid']]['parameters'][] = [ 1277 'name' => $parameter['name'], 1278 'value' => $parameter['value'] 1279 ]; 1280 } 1281 } 1282 1283 return $this->addRelatedGroupsAndHosts($options, $result); 1284 } 1285 1286 /** 1287 * Applies relational subselect onto already fetched result. 1288 * 1289 * @param array $options 1290 * @param mixed $options['selectGroups'] 1291 * @param mixed $options['selectHosts'] 1292 * @param array $result 1293 * @param array $hostids An additional filter by hostids, which will be added to "hosts" key. 1294 * 1295 * @return array $result 1296 */ 1297 private function addRelatedGroupsAndHosts(array $options, array $result, array $hostids = null) { 1298 $is_groups_select = $options['selectGroups'] !== null && $options['selectGroups']; 1299 $is_hosts_select = $options['selectHosts'] !== null && $options['selectHosts']; 1300 1301 if (!$is_groups_select && !$is_hosts_select) { 1302 return $result; 1303 } 1304 1305 $host_groups_with_write_access = []; 1306 $has_write_access_level = false; 1307 1308 $group_search_names = []; 1309 foreach ($result as $script) { 1310 $has_write_access_level |= ($script['host_access'] == PERM_READ_WRITE); 1311 1312 // If any script belongs to all host groups. 1313 if ($script['groupid'] == 0) { 1314 $group_search_names = null; 1315 } 1316 1317 if ($group_search_names !== null) { 1318 /* 1319 * If scripts were requested by host or group filters, then we have already requested group names 1320 * for all groups linked to scripts. And then we can request less groups by adding them as search 1321 * condition in hostgroup.get. Otherwise we will need to request all groups, user has access to. 1322 */ 1323 if (array_key_exists($script['groupid'], $this->parent_host_groups)) { 1324 $group_search_names[] = $this->parent_host_groups[$script['groupid']]['name']; 1325 } 1326 } 1327 } 1328 1329 $select_groups = ['name', 'groupid']; 1330 $select_groups = $this->outputExtend($options['selectGroups'], $select_groups); 1331 1332 $host_groups = API::HostGroup()->get([ 1333 'output' => $select_groups, 1334 'search' => $group_search_names ? ['name' => $group_search_names] : null, 1335 'searchByAny' => true, 1336 'startSearch' => true, 1337 'preservekeys' => true 1338 ]); 1339 1340 if ($has_write_access_level && $host_groups) { 1341 $host_groups_with_write_access = API::HostGroup()->get([ 1342 'output' => $select_groups, 1343 'groupid' => array_keys($host_groups), 1344 'preservekeys' => true, 1345 'editable' => true 1346 ]); 1347 } 1348 else { 1349 $host_groups_with_write_access = $host_groups; 1350 } 1351 1352 $nested = []; 1353 foreach ($host_groups as $groupid => $group) { 1354 $name = $group['name']; 1355 1356 while (($pos = strrpos($name, '/')) !== false) { 1357 $name = substr($name, 0, $pos); 1358 $nested[$name][$groupid] = true; 1359 } 1360 } 1361 1362 $hstgrp_branch = []; 1363 foreach ($host_groups as $groupid => $group) { 1364 $hstgrp_branch[$groupid] = [$groupid => true]; 1365 if (array_key_exists($group['name'], $nested)) { 1366 $hstgrp_branch[$groupid] += $nested[$group['name']]; 1367 } 1368 } 1369 1370 if ($is_hosts_select) { 1371 $sql = 'SELECT hostid,groupid FROM hosts_groups'. 1372 ' WHERE '.dbConditionInt('groupid', array_keys($host_groups)); 1373 if ($hostids !== null) { 1374 $sql .= ' AND '.dbConditionInt('hostid', $hostids); 1375 } 1376 1377 $db_group_hosts = DBSelect($sql); 1378 1379 $all_hostids = []; 1380 $group_to_hosts = []; 1381 while ($row = DBFetch($db_group_hosts)) { 1382 if (!array_key_exists($row['groupid'], $group_to_hosts)) { 1383 $group_to_hosts[$row['groupid']] = []; 1384 } 1385 1386 $group_to_hosts[$row['groupid']][$row['hostid']] = true; 1387 $all_hostids[] = $row['hostid']; 1388 } 1389 1390 $used_hosts = API::Host()->get([ 1391 'output' => $options['selectHosts'], 1392 'hostids' => $all_hostids, 1393 'preservekeys' => true 1394 ]); 1395 } 1396 1397 $host_groups = $this->unsetExtraFields($host_groups, ['name', 'groupid'], $options['selectGroups']); 1398 $host_groups_with_write_access = $this->unsetExtraFields( 1399 $host_groups_with_write_access, ['name', 'groupid'], $options['selectGroups'] 1400 ); 1401 1402 foreach ($result as &$script) { 1403 if ($script['groupid'] == 0) { 1404 $script_groups = ($script['host_access'] == PERM_READ_WRITE) 1405 ? $host_groups_with_write_access 1406 : $host_groups; 1407 } 1408 else { 1409 $script_groups = ($script['host_access'] == PERM_READ_WRITE) 1410 ? array_intersect_key($host_groups_with_write_access, $hstgrp_branch[$script['groupid']]) 1411 : array_intersect_key($host_groups, $hstgrp_branch[$script['groupid']]); 1412 } 1413 1414 if ($is_groups_select) { 1415 $script['groups'] = array_values($script_groups); 1416 } 1417 1418 if ($is_hosts_select) { 1419 $script['hosts'] = []; 1420 foreach (array_keys($script_groups) as $script_groupid) { 1421 if (array_key_exists($script_groupid, $group_to_hosts)) { 1422 $script['hosts'] += array_intersect_key($used_hosts, $group_to_hosts[$script_groupid]); 1423 } 1424 } 1425 $script['hosts'] = array_values($script['hosts']); 1426 } 1427 } 1428 unset($script); 1429 1430 return $result; 1431 } 1432} 1433