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 protected $tableName = 'scripts'; 28 protected $tableAlias = 's'; 29 protected $sortColumns = ['scriptid', 'name']; 30 31 /** 32 * This property, if filled out, will contain all hostrgroup ids 33 * that requested scripts did inherit from. 34 * Keyed by scriptid. 35 * 36 * @var array|HostGroup[] 37 */ 38 protected $parent_host_groups = []; 39 40 /** 41 * @param array $options 42 * 43 * @throws APIException if the input is invalid. 44 * 45 * @return array|int 46 */ 47 public function get(array $options) { 48 $script_fields = ['scriptid', 'name', 'command', 'host_access', 'usrgrpid', 'groupid', 'description', 49 'confirmation', 'type', 'execute_on' 50 ]; 51 $group_fields = ['groupid', 'name', 'flags', 'internal']; 52 $host_fields = ['hostid', 'host', 'name', 'description', 'status', 'proxy_hostid', 'inventory_mode', 'flags', 53 'available', 'snmp_available', 'jmx_available', 'ipmi_available', 'error', 'snmp_error', 'jmx_error', 54 'ipmi_error', 'errors_from', 'snmp_errors_from', 'jmx_errors_from', 'ipmi_errors_from', 'disable_until', 55 'snmp_disable_until', 'jmx_disable_until', 'ipmi_disable_until', 'ipmi_authtype', 'ipmi_privilege', 56 'ipmi_username', 'ipmi_password', 'maintenanceid', 'maintenance_status', 'maintenance_type', 57 'maintenance_from', 'tls_connect', 'tls_accept', 'tls_issuer', 'tls_subject', 'tls_psk_identity', 'tls_psk' 58 ]; 59 60 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 61 // filter 62 'scriptids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 63 'hostids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 64 'groupids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 65 'usrgrpids' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null], 66 'filter' => ['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [ 67 'scriptid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 68 'name' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 69 'command' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 70 'host_access' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', [PERM_READ, PERM_READ_WRITE])], 71 'usrgrpid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 72 'groupid' => ['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 73 'confirmation' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 74 'type' => ['type' => API_INTS32, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'in' => implode(',', [ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT, ZBX_SCRIPT_TYPE_IPMI])], 75 '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])] 76 ]], 77 'search' => ['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [ 78 'name' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 79 'command' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 80 'description' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE], 81 'confirmation' => ['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE] 82 ]], 83 'searchByAny' => ['type' => API_BOOLEAN, 'default' => false], 84 'startSearch' => ['type' => API_FLAG, 'default' => false], 85 'excludeSearch' => ['type' => API_FLAG, 'default' => false], 86 'searchWildcardsEnabled' => ['type' => API_BOOLEAN, 'default' => false], 87 // output 88 'output' => ['type' => API_OUTPUT, 'in' => implode(',', $script_fields), 'default' => API_OUTPUT_EXTEND], 89 'selectGroups' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $group_fields), 'default' => null], 90 'selectHosts' => ['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL, 'in' => implode(',', $host_fields), 'default' => null], 91 'countOutput' => ['type' => API_FLAG, 'default' => false], 92 // sort and limit 93 'sortfield' => ['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []], 94 'sortorder' => ['type' => API_SORTORDER, 'default' => []], 95 'limit' => ['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null], 96 // flags 97 'editable' => ['type' => API_BOOLEAN, 'default' => false], 98 'preservekeys' => ['type' => API_BOOLEAN, 'default' => false] 99 ]]; 100 if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) { 101 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 102 } 103 104 $sql_parts = [ 105 'select' => ['scripts' => 's.scriptid'], 106 'from' => ['scripts' => 'scripts s'], 107 'where' => [], 108 'order' => [] 109 ]; 110 111 // editable + permission check 112 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 113 if ($options['editable']) { 114 return $options['countOutput'] ? 0 : []; 115 } 116 117 $user_groups = getUserGroupsByUserId(self::$userData['userid']); 118 119 $sql_parts['where'][] = '(s.usrgrpid IS NULL OR '.dbConditionInt('s.usrgrpid', $user_groups).')'; 120 $sql_parts['where'][] = '(s.groupid IS NULL OR EXISTS ('. 121 'SELECT NULL'. 122 ' FROM rights r'. 123 ' WHERE s.groupid=r.id'. 124 ' AND '.dbConditionInt('r.groupid', $user_groups). 125 ' GROUP BY r.id'. 126 ' HAVING MIN(r.permission)>'.PERM_DENY. 127 '))'; 128 } 129 130 $host_groups = null; 131 $host_groups_by_hostids = null; 132 $host_groups_by_groupids = null; 133 134 // Hostids and groupids selection API calls must be made separately because we must intersect enriched groupids. 135 if ($options['hostids'] !== null) { 136 $host_groups_by_hostids = enrichParentGroups(API::HostGroup()->get([ 137 'output' => ['groupid', 'name'], 138 'hostids' => $options['hostids'], 139 'preservekeys' => true 140 ])); 141 } 142 if ($options['groupids'] !== null) { 143 $host_groups_by_groupids = enrichParentGroups(API::HostGroup()->get([ 144 'output' => ['groupid', 'name'], 145 'groupids' => $options['groupids'], 146 'preservekeys' => true 147 ])); 148 } 149 150 if ($host_groups_by_groupids !== null && $host_groups_by_hostids !== null) { 151 $host_groups = array_intersect_key($host_groups_by_hostids, $host_groups_by_groupids); 152 } 153 elseif ($host_groups_by_hostids !== null) { 154 $host_groups = $host_groups_by_hostids; 155 } 156 elseif ($host_groups_by_groupids !== null) { 157 $host_groups = $host_groups_by_groupids; 158 } 159 160 if ($host_groups !== null) { 161 $sql_parts['where'][] = '('.dbConditionInt('s.groupid', array_keys($host_groups)).' OR s.groupid IS NULL)'; 162 $this->parent_host_groups = $host_groups; 163 } 164 165 // usrgrpids 166 if ($options['usrgrpids'] !== null) { 167 $sql_parts['where'][] = '(s.usrgrpid IS NULL OR '.dbConditionInt('s.usrgrpid', $options['usrgrpids']).')'; 168 } 169 170 // scriptids 171 if ($options['scriptids'] !== null) { 172 $sql_parts['where'][] = dbConditionInt('s.scriptid', $options['scriptids']); 173 } 174 175 // search 176 if ($options['search'] !== null) { 177 zbx_db_search('scripts s', $options, $sql_parts); 178 } 179 180 // filter 181 if ($options['filter'] !== null) { 182 $this->dbFilter('scripts s', $options, $sql_parts); 183 } 184 185 $db_scripts = []; 186 187 $sql_parts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts); 188 $sql_parts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts); 189 190 $result = DBselect(self::createSelectQueryFromParts($sql_parts), $options['limit']); 191 192 while ($db_script = DBfetch($result)) { 193 if ($options['countOutput']) { 194 return $db_script['rowscount']; 195 } 196 197 $db_scripts[$db_script['scriptid']] = $db_script; 198 } 199 200 if ($db_scripts) { 201 $db_scripts = $this->addRelatedObjects($options, $db_scripts); 202 $db_scripts = $this->unsetExtraFields($db_scripts, ['scriptid', 'groupid', 'host_access'], 203 $options['output'] 204 ); 205 206 if (!$options['preservekeys']) { 207 $db_scripts = array_values($db_scripts); 208 } 209 } 210 211 return $db_scripts; 212 } 213 214 /** 215 * @param array $scripts 216 * 217 * @return array 218 */ 219 public function create(array $scripts) { 220 $this->validateCreate($scripts); 221 222 $scriptids = DB::insert('scripts', $scripts); 223 224 foreach ($scripts as $index => &$script) { 225 $script['scriptid'] = $scriptids[$index]; 226 } 227 unset($script); 228 229 $this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_SCRIPT, $scripts); 230 231 return ['scriptids' => $scriptids]; 232 } 233 234 /** 235 * @param array $scripts 236 * 237 * @throws APIException if the input is invalid 238 */ 239 protected function validateCreate(array &$scripts) { 240 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 241 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 242 } 243 244 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['name']], 'fields' => [ 245 'name' => ['type' => API_SCRIPT_NAME, 'flags' => API_REQUIRED, 'length' => DB::getFieldLength('scripts', 'name')], 246 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT, ZBX_SCRIPT_TYPE_IPMI])], 247 'execute_on' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SCRIPT_EXECUTE_ON_AGENT, ZBX_SCRIPT_EXECUTE_ON_SERVER, ZBX_SCRIPT_EXECUTE_ON_PROXY])], 248 'command' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('scripts', 'command')], 249 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('scripts', 'description')], 250 'usrgrpid' => ['type' => API_ID], 251 'groupid' => ['type' => API_ID], 252 'host_access' => ['type' => API_INT32, 'in' => implode(',', [PERM_READ, PERM_READ_WRITE])], 253 'confirmation' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('scripts', 'confirmation')] 254 ]]; 255 if (!CApiInputValidator::validate($api_input_rules, $scripts, '/', $error)) { 256 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 257 } 258 259 $scripts = $this->checkExecutionType($scripts); 260 $this->checkUserGroups($scripts); 261 $this->checkHostGroups($scripts); 262 $this->checkDuplicates($scripts); 263 } 264 265 /** 266 * @param array $scripts 267 * 268 * @return array 269 */ 270 public function update(array $scripts) { 271 $this->validateUpdate($scripts, $db_scripts); 272 273 $upd_scripts = []; 274 275 foreach ($scripts as $script) { 276 $db_script = $db_scripts[$script['scriptid']]; 277 278 $upd_script = []; 279 280 // strings 281 foreach (['name', 'command', 'description', 'confirmation'] as $field_name) { 282 if (array_key_exists($field_name, $script) && $script[$field_name] !== $db_script[$field_name]) { 283 $upd_script[$field_name] = $script[$field_name]; 284 } 285 } 286 // integers 287 foreach (['type', 'execute_on', 'usrgrpid', 'groupid', 'host_access'] as $field_name) { 288 if (array_key_exists($field_name, $script) && $script[$field_name] != $db_script[$field_name]) { 289 $upd_script[$field_name] = $script[$field_name]; 290 } 291 } 292 293 if ($upd_script) { 294 $upd_scripts[] = [ 295 'values' => $upd_script, 296 'where' => ['scriptid' => $script['scriptid']] 297 ]; 298 } 299 } 300 301 if ($upd_scripts) { 302 DB::update('scripts', $upd_scripts); 303 } 304 305 $this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_SCRIPT, $scripts, $db_scripts); 306 307 return ['scriptids' => zbx_objectValues($scripts, 'scriptid')]; 308 } 309 310 /** 311 * @param array $scripts 312 * @param array $db_scripts 313 * 314 * @throws APIException if the input is invalid 315 */ 316 protected function validateUpdate(array &$scripts, array &$db_scripts = null) { 317 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 318 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 319 } 320 321 $api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['scriptid'], ['name']], 'fields' => [ 322 'scriptid' => ['type' => API_ID, 'flags' => API_REQUIRED], 323 'name' => ['type' => API_SCRIPT_NAME, 'length' => DB::getFieldLength('scripts', 'name')], 324 'type' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT, ZBX_SCRIPT_TYPE_IPMI])], 325 'execute_on' => ['type' => API_INT32, 'in' => implode(',', [ZBX_SCRIPT_EXECUTE_ON_AGENT, ZBX_SCRIPT_EXECUTE_ON_SERVER, ZBX_SCRIPT_EXECUTE_ON_PROXY])], 326 'command' => ['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('scripts', 'command')], 327 'description' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('scripts', 'description')], 328 'usrgrpid' => ['type' => API_ID], 329 'groupid' => ['type' => API_ID], 330 'host_access' => ['type' => API_INT32, 'in' => implode(',', [PERM_READ, PERM_READ_WRITE])], 331 'confirmation' => ['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('scripts', 'confirmation')] 332 ]]; 333 if (!CApiInputValidator::validate($api_input_rules, $scripts, '/', $error)) { 334 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 335 } 336 337 $db_scripts = DB::select('scripts', [ 338 'output' => ['scriptid', 'name', 'type', 'execute_on', 'command', 'description', 'usrgrpid', 'groupid', 339 'host_access', 'confirmation' 340 ], 341 'scriptids' => zbx_objectValues($scripts, 'scriptid'), 342 'preservekeys' => true 343 ]); 344 345 $new_name_scripts = []; 346 347 foreach ($scripts as $script) { 348 if (!array_key_exists($script['scriptid'], $db_scripts)) { 349 self::exception(ZBX_API_ERROR_PERMISSIONS, 350 _('No permissions to referred object or it does not exist!') 351 ); 352 } 353 354 $db_script = $db_scripts[$script['scriptid']]; 355 356 if (array_key_exists('name', $script) && trimPath($script['name']) !== trimPath($db_script['name'])) { 357 $new_name_scripts[] = $script; 358 } 359 } 360 361 $scripts = $this->checkExecutionType($scripts); 362 $this->checkUserGroups($scripts); 363 $this->checkHostGroups($scripts); 364 if ($new_name_scripts) { 365 $this->checkDuplicates($new_name_scripts); 366 } 367 } 368 369 /** 370 * Validate incompatible options ZBX_SCRIPT_TYPE_IPMI and ZBX_SCRIPT_EXECUTE_ON_AGENT. 371 * 372 * @param array $scripts 373 * 374 * @return array 375 */ 376 private function checkExecutionType(array $scripts) { 377 foreach ($scripts as &$script) { 378 if (array_key_exists('type', $script) && $script['type'] == ZBX_SCRIPT_TYPE_IPMI) { 379 if (!array_key_exists('execute_on', $script)) { 380 $script['execute_on'] = ZBX_SCRIPT_EXECUTE_ON_SERVER; 381 } 382 elseif ($script['execute_on'] == ZBX_SCRIPT_EXECUTE_ON_AGENT) { 383 self::exception(ZBX_API_ERROR_PARAMETERS, _('IPMI scripts can be executed only by server.')); 384 } 385 } 386 } 387 unset($script); 388 389 return $scripts; 390 } 391 392 /** 393 * Check for valid user groups. 394 * 395 * @param array $scripts 396 * @param array $scripts[]['usrgrpid'] (optional) 397 * 398 * @throws APIException if user group is not exists. 399 */ 400 private function checkUserGroups(array $scripts) { 401 $usrgrpids = []; 402 403 foreach ($scripts as $script) { 404 if (array_key_exists('usrgrpid', $script) && $script['usrgrpid'] != 0) { 405 $usrgrpids[$script['usrgrpid']] = true; 406 } 407 } 408 409 if (!$usrgrpids) { 410 return; 411 } 412 413 $usrgrpids = array_keys($usrgrpids); 414 415 $db_usrgrps = DB::select('usrgrp', [ 416 'output' => [], 417 'usrgrpids' => $usrgrpids, 418 'preservekeys' => true 419 ]); 420 421 foreach ($usrgrpids as $usrgrpid) { 422 if (!array_key_exists($usrgrpid, $db_usrgrps)) { 423 self::exception(ZBX_API_ERROR_PARAMETERS, _s('User group with ID "%1$s" is not available.', $usrgrpid)); 424 } 425 } 426 } 427 428 /** 429 * Check for valid host groups. 430 * 431 * @param array $scripts 432 * @param array $scripts[]['groupid'] (optional) 433 * 434 * @throws APIException if host group is not exists. 435 */ 436 private function checkHostGroups(array $scripts) { 437 $groupids = []; 438 439 foreach ($scripts as $script) { 440 if (array_key_exists('groupid', $script) && $script['groupid'] != 0) { 441 $groupids[$script['groupid']] = true; 442 } 443 } 444 445 if (!$groupids) { 446 return; 447 } 448 449 $groupids = array_keys($groupids); 450 451 $db_groups = DB::select('hstgrp', [ 452 'output' => [], 453 'groupids' => $groupids, 454 'preservekeys' => true 455 ]); 456 457 foreach ($groupids as $groupid) { 458 if (!array_key_exists($groupid, $db_groups)) { 459 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host group with ID "%1$s" is not available.', $groupid)); 460 } 461 } 462 } 463 464 /** 465 * Auxiliary function for checkDuplicates(). 466 * 467 * @param array $folders 468 * @param string $name 469 * @param array $db_folders 470 * @param string $db_name 471 * 472 * @throws APIException 473 */ 474 private static function checkScriptNames(array $folders, $name, array $db_folders, $db_name) { 475 if (array_slice($folders, 0, count($db_folders)) === $db_folders) { 476 self::exception(ZBX_API_ERROR_PARAMETERS, 477 _s('Script menu path "%1$s" already used in script name "%2$s".', $name, $db_name) 478 ); 479 } 480 481 if (array_slice($db_folders, 0, count($folders)) === $folders) { 482 self::exception(ZBX_API_ERROR_PARAMETERS, 483 _s('Script name "%1$s" already used in menu path for script "%2$s".', $name, $db_name) 484 ); 485 } 486 } 487 488 /** 489 * Check for duplicated scripts. 490 * 491 * @param array $scripts 492 * @param string $scripts['scriptid'] 493 * @param string $scripts['name'] 494 * 495 * @throws APIException if global script already exists. 496 */ 497 private function checkDuplicates(array $scripts) { 498 $db_scripts = DB::select('scripts', [ 499 'output' => ['scriptid', 'name'] 500 ]); 501 502 $uniq_names = []; 503 504 foreach ($db_scripts as &$db_script) { 505 $db_script['folders'] = array_map('trim', splitPath($db_script['name'])); 506 $uniq_names[implode('/', $db_script['folders'])] = true; 507 } 508 unset($db_script); 509 510 $ok_scripts = []; 511 512 foreach ($scripts as $script) { 513 $script['folders'] = array_map('trim', splitPath($script['name'])); 514 $uniq_name = implode('/', $script['folders']); 515 516 if (array_key_exists($uniq_name, $uniq_names)) { 517 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Script "%1$s" already exists.', $script['name'])); 518 } 519 $uniq_names[$uniq_name] = true; 520 521 foreach ($ok_scripts as $ok_script) { 522 self::checkScriptNames($script['folders'], $script['name'], $ok_script['folders'], $ok_script['name']); 523 } 524 525 foreach ($db_scripts as $db_script) { 526 if (array_key_exists('scriptid', $script) && bccomp($script['scriptid'], $db_script['scriptid']) == 0) { 527 continue; 528 } 529 530 self::checkScriptNames($script['folders'], $script['name'], $db_script['folders'], $db_script['name']); 531 } 532 533 $ok_scripts[] = $script; 534 } 535 } 536 537 /** 538 * @param array $scriptids 539 * 540 * @return array 541 */ 542 public function delete(array $scriptids) { 543 $this->validateDelete($scriptids, $db_scripts); 544 545 DB::delete('scripts', ['scriptid' => $scriptids]); 546 547 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_SCRIPT, $db_scripts); 548 549 return ['scriptids' => $scriptids]; 550 } 551 552 /** 553 * @param array $scriptids 554 * @param array $db_scripts 555 * 556 * @throws APIException if the input is invalid 557 */ 558 protected function validateDelete(array &$scriptids, array &$db_scripts = null) { 559 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 560 self::exception(ZBX_API_ERROR_PERMISSIONS, _('You do not have permission to perform this operation.')); 561 } 562 563 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 564 if (!CApiInputValidator::validate($api_input_rules, $scriptids, '/', $error)) { 565 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 566 } 567 568 $db_scripts = DB::select('scripts', [ 569 'output' => ['scriptid', 'name'], 570 'scriptids' => $scriptids, 571 'preservekeys' => true 572 ]); 573 574 foreach ($scriptids as $scriptid) { 575 if (!array_key_exists($scriptid, $db_scripts)) { 576 self::exception(ZBX_API_ERROR_PERMISSIONS, 577 _('No permissions to referred object or it does not exist!') 578 ); 579 } 580 } 581 582 // Check if deleted scripts used in actions. 583 $db_actions = DBselect( 584 'SELECT a.name,oc.scriptid'. 585 ' FROM opcommand oc,operations o,actions a'. 586 ' WHERE oc.operationid=o.operationid'. 587 ' AND o.actionid=a.actionid'. 588 ' AND '.dbConditionInt('oc.scriptid', $scriptids), 589 1 590 ); 591 592 if ($db_action = DBfetch($db_actions)) { 593 self::exception(ZBX_API_ERROR_PARAMETERS, 594 _s('Cannot delete scripts. Script "%1$s" is used in action operation "%2$s".', 595 $db_scripts[$db_action['scriptid']]['name'], $db_action['name'] 596 ) 597 ); 598 } 599 } 600 601 /** 602 * @param array $data 603 * 604 * @return array 605 */ 606 public function execute(array $data) { 607 global $ZBX_SERVER, $ZBX_SERVER_PORT; 608 609 $api_input_rules = ['type' => API_OBJECT, 'fields' => [ 610 'hostid' => ['type' => API_ID, 'flags' => API_REQUIRED], 611 'scriptid' => ['type' => API_ID, 'flags' => API_REQUIRED] 612 ]]; 613 if (!CApiInputValidator::validate($api_input_rules, $data, '/', $error)) { 614 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 615 } 616 617 $db_hosts = API::Host()->get([ 618 'output' => [], 619 'hostids' => $data['hostid'] 620 ]); 621 if (!$db_hosts) { 622 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 623 } 624 625 $db_scripts = $this->get([ 626 'output' => [], 627 'hostids' => $data['hostid'], 628 'scriptids' => $data['scriptid'] 629 ]); 630 if (!$db_scripts) { 631 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 632 } 633 634 // execute script 635 $zabbix_server = new CZabbixServer($ZBX_SERVER, $ZBX_SERVER_PORT, ZBX_SCRIPT_TIMEOUT, ZBX_SOCKET_BYTES_LIMIT); 636 $result = $zabbix_server->executeScript($data['scriptid'], $data['hostid'], self::$userData['sessionid']); 637 638 if ($result !== false) { 639 // return the result in a backwards-compatible format 640 return [ 641 'response' => 'success', 642 'value' => $result 643 ]; 644 } 645 else { 646 self::exception(ZBX_API_ERROR_INTERNAL, $zabbix_server->getError()); 647 } 648 } 649 650 /** 651 * Returns all the scripts that are available on each given host. 652 * 653 * @param $hostids 654 * 655 * @return array 656 */ 657 public function getScriptsByHosts($hostids) { 658 zbx_value2array($hostids); 659 660 $scripts_by_host = []; 661 662 if (!$hostids) { 663 return $scripts_by_host; 664 } 665 666 foreach ($hostids as $hostid) { 667 $scripts_by_host[$hostid] = []; 668 } 669 670 $scripts = $this->get([ 671 'output' => API_OUTPUT_EXTEND, 672 'hostids' => $hostids, 673 'sortfield' => 'name', 674 'preservekeys' => true 675 ]); 676 677 $scripts = $this->addRelatedGroupsAndHosts([ 678 'selectGroups' => null, 679 'selectHosts' => ['hostid'] 680 ], $scripts, $hostids); 681 682 if ($scripts) { 683 // resolve macros 684 $macros_data = []; 685 foreach ($scripts as $scriptid => $script) { 686 if (!empty($script['confirmation'])) { 687 foreach ($script['hosts'] as $host) { 688 if (isset($scripts_by_host[$host['hostid']])) { 689 $macros_data[$host['hostid']][$scriptid] = $script['confirmation']; 690 } 691 } 692 } 693 } 694 if ($macros_data) { 695 $macros_data = CMacrosResolverHelper::resolve([ 696 'config' => 'scriptConfirmation', 697 'data' => $macros_data 698 ]); 699 } 700 701 foreach ($scripts as $scriptid => $script) { 702 $hosts = $script['hosts']; 703 unset($script['hosts']); 704 // set script to host 705 foreach ($hosts as $host) { 706 $hostid = $host['hostid']; 707 708 if (isset($scripts_by_host[$hostid])) { 709 $size = count($scripts_by_host[$hostid]); 710 $scripts_by_host[$hostid][$size] = $script; 711 712 // set confirmation text with resolved macros 713 if (isset($macros_data[$hostid][$scriptid]) && $script['confirmation']) { 714 $scripts_by_host[$hostid][$size]['confirmation'] = $macros_data[$hostid][$scriptid]; 715 } 716 } 717 } 718 } 719 } 720 721 return $scripts_by_host; 722 } 723 724 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 725 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 726 727 if ($options['selectGroups'] !== null || $options['selectHosts'] !== null) { 728 $sqlParts = $this->addQuerySelect($this->fieldId('groupid'), $sqlParts); 729 $sqlParts = $this->addQuerySelect($this->fieldId('host_access'), $sqlParts); 730 } 731 732 return $sqlParts; 733 } 734 735 /** 736 * Applies relational subselect onto already fetched result. 737 * 738 * @param array $options 739 * @param array $result 740 * 741 * @return array $result 742 */ 743 protected function addRelatedObjects(array $options, array $result) { 744 $result = parent::addRelatedObjects($options, $result); 745 746 return $this->addRelatedGroupsAndHosts($options, $result); 747 } 748 749 /** 750 * Applies relational subselect onto already fetched result. 751 * 752 * @param array $options 753 * @param mixed $options['selectGroups'] 754 * @param mixed $options['selectHosts'] 755 * @param array $result 756 * @param array $hostids An additional filter by hostids, which will be added to "hosts" key. 757 * 758 * @return array $result 759 */ 760 private function addRelatedGroupsAndHosts(array $options, array $result, array $hostids = null) { 761 $is_groups_select = $options['selectGroups'] !== null && $options['selectGroups']; 762 $is_hosts_select = $options['selectHosts'] !== null && $options['selectHosts']; 763 764 if (!$is_groups_select && !$is_hosts_select) { 765 return $result; 766 } 767 768 $host_groups_with_write_access = []; 769 $has_write_access_level = false; 770 771 $group_search_names = []; 772 foreach ($result as $script) { 773 $has_write_access_level |= ($script['host_access'] == PERM_READ_WRITE); 774 775 // If any script belongs to all host groups. 776 if ($script['groupid'] == 0) { 777 $group_search_names = null; 778 } 779 780 if ($group_search_names !== null) { 781 /* 782 * If scripts were requested by host or group filters, then we have already requested group names 783 * for all groups linked to scripts. And then we can request less groups by adding them as search 784 * condition in hostgroup.get. Otherwise we will need to request all groups, user has access to. 785 */ 786 if (array_key_exists($script['groupid'], $this->parent_host_groups)) { 787 $group_search_names[] = $this->parent_host_groups[$script['groupid']]['name']; 788 } 789 } 790 } 791 792 $select_groups = ['name', 'groupid']; 793 $select_groups = $this->outputExtend($options['selectGroups'], $select_groups); 794 795 $host_groups = API::HostGroup()->get([ 796 'output' => $select_groups, 797 'search' => $group_search_names ? ['name' => $group_search_names] : null, 798 'searchByAny' => true, 799 'startSearch' => true, 800 'preservekeys' => true 801 ]); 802 803 if ($has_write_access_level && $host_groups) { 804 $host_groups_with_write_access = API::HostGroup()->get([ 805 'output' => $select_groups, 806 'groupid' => array_keys($host_groups), 807 'preservekeys' => true, 808 'editable' => true 809 ]); 810 } 811 else { 812 $host_groups_with_write_access = $host_groups; 813 } 814 815 $nested = []; 816 foreach ($host_groups as $groupid => $group) { 817 $name = $group['name']; 818 819 while (($pos = strrpos($name, '/')) !== false) { 820 $name = substr($name, 0, $pos); 821 $nested[$name][$groupid] = true; 822 } 823 } 824 825 $hstgrp_branch = []; 826 foreach ($host_groups as $groupid => $group) { 827 $hstgrp_branch[$groupid] = [$groupid => true]; 828 if (array_key_exists($group['name'], $nested)) { 829 $hstgrp_branch[$groupid] += $nested[$group['name']]; 830 } 831 } 832 833 if ($is_hosts_select) { 834 $sql = 'SELECT hostid,groupid FROM hosts_groups'. 835 ' WHERE '.dbConditionInt('groupid', array_keys($host_groups)); 836 if ($hostids !== null) { 837 $sql .= ' AND '.dbConditionInt('hostid', $hostids); 838 } 839 840 $db_group_hosts = DBSelect($sql); 841 842 $all_hostids = []; 843 $group_to_hosts = []; 844 while ($row = DBFetch($db_group_hosts)) { 845 if (!array_key_exists($row['groupid'], $group_to_hosts)) { 846 $group_to_hosts[$row['groupid']] = []; 847 } 848 849 $group_to_hosts[$row['groupid']][$row['hostid']] = true; 850 $all_hostids[] = $row['hostid']; 851 } 852 853 $used_hosts = API::Host()->get([ 854 'output' => $options['selectHosts'], 855 'hostids' => $all_hostids, 856 'preservekeys' => true 857 ]); 858 } 859 860 $host_groups = $this->unsetExtraFields($host_groups, ['name', 'groupid'], $options['selectGroups']); 861 $host_groups_with_write_access = $this->unsetExtraFields( 862 $host_groups_with_write_access, ['name', 'groupid'], $options['selectGroups'] 863 ); 864 865 foreach ($result as &$script) { 866 if ($script['groupid'] == 0) { 867 $script_groups = ($script['host_access'] == PERM_READ_WRITE) 868 ? $host_groups_with_write_access 869 : $host_groups; 870 } 871 else { 872 $script_groups = ($script['host_access'] == PERM_READ_WRITE) 873 ? array_intersect_key($host_groups_with_write_access, $hstgrp_branch[$script['groupid']]) 874 : array_intersect_key($host_groups, $hstgrp_branch[$script['groupid']]); 875 } 876 877 if ($is_groups_select) { 878 $script['groups'] = array_values($script_groups); 879 } 880 881 if ($is_hosts_select) { 882 $script['hosts'] = []; 883 foreach (array_keys($script_groups) as $script_groupid) { 884 if (array_key_exists($script_groupid, $group_to_hosts)) { 885 $script['hosts'] += array_intersect_key($used_hosts, $group_to_hosts[$script_groupid]); 886 } 887 } 888 $script['hosts'] = array_values($script['hosts']); 889 } 890 } 891 unset($script); 892 893 return $result; 894 } 895} 896