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 host interfaces. 24 */ 25class CHostInterface extends CApiService { 26 27 protected $tableName = 'interface'; 28 protected $tableAlias = 'hi'; 29 protected $sortColumns = ['interfaceid', 'dns', 'ip']; 30 31 /** 32 * Get interface data. 33 * 34 * @param array $options 35 * @param array $options['hostids'] Interface IDs 36 * @param bool $options['editable'] only with read-write permission. Ignored for SuperAdmins 37 * @param bool $options['selectHosts'] select Interface hosts 38 * @param bool $options['selectItems'] select Items 39 * @param int $options['count'] count Interfaces, returned column name is rowscount 40 * @param string $options['pattern'] search hosts by pattern in Interface name 41 * @param int $options['limit'] limit selection 42 * @param string $options['sortfield'] field to sort by 43 * @param string $options['sortorder'] sort order 44 * 45 * @return array|boolean Interface data as array or false if error 46 */ 47 public function get(array $options = []) { 48 $result = []; 49 50 $sqlParts = [ 51 'select' => ['interface' => 'hi.interfaceid'], 52 'from' => ['interface' => 'interface hi'], 53 'where' => [], 54 'group' => [], 55 'order' => [], 56 'limit' => null 57 ]; 58 59 $defOptions = [ 60 'groupids' => null, 61 'hostids' => null, 62 'interfaceids' => null, 63 'itemids' => null, 64 'triggerids' => null, 65 'editable' => false, 66 'nopermissions' => null, 67 // filter 68 'filter' => null, 69 'search' => null, 70 'searchByAny' => null, 71 'startSearch' => false, 72 'excludeSearch' => false, 73 'searchWildcardsEnabled' => null, 74 // output 75 'output' => API_OUTPUT_EXTEND, 76 'selectHosts' => null, 77 'selectItems' => null, 78 'countOutput' => false, 79 'groupCount' => false, 80 'preservekeys' => false, 81 'sortfield' => '', 82 'sortorder' => '', 83 'limit' => null, 84 'limitSelects' => null 85 ]; 86 $options = zbx_array_merge($defOptions, $options); 87 88 // editable + PERMISSION CHECK 89 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 90 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 91 $userGroups = getUserGroupsByUserId(self::$userData['userid']); 92 93 $sqlParts['where'][] = 'EXISTS ('. 94 'SELECT NULL'. 95 ' FROM hosts_groups hgg'. 96 ' JOIN rights r'. 97 ' ON r.id=hgg.groupid'. 98 ' AND '.dbConditionInt('r.groupid', $userGroups). 99 ' WHERE hi.hostid=hgg.hostid'. 100 ' GROUP BY hgg.hostid'. 101 ' HAVING MIN(r.permission)>'.PERM_DENY. 102 ' AND MAX(r.permission)>='.zbx_dbstr($permission). 103 ')'; 104 } 105 106 // interfaceids 107 if (!is_null($options['interfaceids'])) { 108 zbx_value2array($options['interfaceids']); 109 $sqlParts['where']['interfaceid'] = dbConditionInt('hi.interfaceid', $options['interfaceids']); 110 } 111 112 // hostids 113 if (!is_null($options['hostids'])) { 114 zbx_value2array($options['hostids']); 115 $sqlParts['where']['hostid'] = dbConditionInt('hi.hostid', $options['hostids']); 116 } 117 118 // itemids 119 if (!is_null($options['itemids'])) { 120 zbx_value2array($options['itemids']); 121 122 $sqlParts['from']['items'] = 'items i'; 123 $sqlParts['where'][] = dbConditionInt('i.itemid', $options['itemids']); 124 $sqlParts['where']['hi'] = 'hi.interfaceid=i.interfaceid'; 125 } 126 127 // triggerids 128 if (!is_null($options['triggerids'])) { 129 zbx_value2array($options['triggerids']); 130 131 $sqlParts['from']['functions'] = 'functions f'; 132 $sqlParts['from']['items'] = 'items i'; 133 $sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']); 134 $sqlParts['where']['hi'] = 'hi.hostid=i.hostid'; 135 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 136 } 137 138 // search 139 if (is_array($options['search'])) { 140 zbx_db_search('interface hi', $options, $sqlParts); 141 } 142 143 // filter 144 if (is_array($options['filter'])) { 145 $this->dbFilter('interface hi', $options, $sqlParts); 146 } 147 148 // limit 149 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 150 $sqlParts['limit'] = $options['limit']; 151 } 152 153 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 154 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 155 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 156 while ($interface = DBfetch($res)) { 157 if ($options['countOutput']) { 158 if ($options['groupCount']) { 159 $result[] = $interface; 160 } 161 else { 162 $result = $interface['rowscount']; 163 } 164 } 165 else { 166 $result[$interface['interfaceid']] = $interface; 167 } 168 } 169 170 if ($options['countOutput']) { 171 return $result; 172 } 173 174 if ($result) { 175 $result = $this->addRelatedObjects($options, $result); 176 $result = $this->unsetExtraFields($result, ['hostid'], $options['output']); 177 } 178 179 // removing keys (hash -> array) 180 if (!$options['preservekeys']) { 181 $result = zbx_cleanHashes($result); 182 } 183 184 return $result; 185 } 186 187 /** 188 * Check interfaces input. 189 * 190 * @param array $interfaces 191 * @param string $method 192 */ 193 public function checkInput(array &$interfaces, $method) { 194 $update = ($method == 'update'); 195 196 // permissions 197 if ($update) { 198 $interfaceDBfields = ['interfaceid' => null]; 199 $dbInterfaces = $this->get([ 200 'output' => API_OUTPUT_EXTEND, 201 'interfaceids' => zbx_objectValues($interfaces, 'interfaceid'), 202 'editable' => true, 203 'preservekeys' => true 204 ]); 205 } 206 else { 207 $interfaceDBfields = [ 208 'hostid' => null, 209 'ip' => null, 210 'dns' => null, 211 'useip' => null, 212 'port' => null, 213 'main' => null 214 ]; 215 } 216 217 $dbHosts = API::Host()->get([ 218 'output' => ['host'], 219 'hostids' => zbx_objectValues($interfaces, 'hostid'), 220 'editable' => true, 221 'preservekeys' => true 222 ]); 223 224 $dbProxies = API::Proxy()->get([ 225 'output' => ['host'], 226 'proxyids' => zbx_objectValues($interfaces, 'hostid'), 227 'editable' => true, 228 'preservekeys' => true 229 ]); 230 231 $check_have_items = []; 232 foreach ($interfaces as &$interface) { 233 if (!check_db_fields($interfaceDBfields, $interface)) { 234 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 235 } 236 237 if ($update) { 238 if (!isset($dbInterfaces[$interface['interfaceid']])) { 239 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 240 } 241 242 $dbInterface = $dbInterfaces[$interface['interfaceid']]; 243 if (isset($interface['hostid']) && bccomp($dbInterface['hostid'], $interface['hostid']) != 0) { 244 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Cannot switch host for interface.')); 245 } 246 247 if (array_key_exists('type', $interface) && $interface['type'] != $dbInterface['type']) { 248 $check_have_items[] = $interface['interfaceid']; 249 } 250 251 $interface['hostid'] = $dbInterface['hostid']; 252 253 // we check all fields on "updated" interface 254 $updInterface = $interface; 255 $interface = zbx_array_merge($dbInterface, $interface); 256 } 257 else { 258 if (!isset($dbHosts[$interface['hostid']]) && !isset($dbProxies[$interface['hostid']])) { 259 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 260 } 261 262 if (isset($dbProxies[$interface['hostid']])) { 263 $interface['type'] = INTERFACE_TYPE_UNKNOWN; 264 } 265 elseif (!isset($interface['type'])) { 266 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to method.')); 267 } 268 } 269 270 if (zbx_empty($interface['ip']) && zbx_empty($interface['dns'])) { 271 self::exception(ZBX_API_ERROR_PARAMETERS, _('IP and DNS cannot be empty for host interface.')); 272 } 273 274 if ($interface['useip'] == INTERFACE_USE_IP && zbx_empty($interface['ip'])) { 275 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Interface with DNS "%1$s" cannot have empty IP address.', $interface['dns'])); 276 } 277 278 if ($interface['useip'] == INTERFACE_USE_DNS && zbx_empty($interface['dns'])) { 279 if ($dbHosts && !empty($dbHosts[$interface['hostid']]['host'])) { 280 self::exception(ZBX_API_ERROR_PARAMETERS, 281 _s('Interface with IP "%1$s" cannot have empty DNS name while having "Use DNS" property on "%2$s".', 282 $interface['ip'], 283 $dbHosts[$interface['hostid']]['host'] 284 )); 285 } 286 elseif ($dbProxies && !empty($dbProxies[$interface['hostid']]['host'])) { 287 self::exception(ZBX_API_ERROR_PARAMETERS, 288 _s('Interface with IP "%1$s" cannot have empty DNS name while having "Use DNS" property on "%2$s".', 289 $interface['ip'], 290 $dbProxies[$interface['hostid']]['host'] 291 )); 292 } 293 else { 294 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Interface with IP "%1$s" cannot have empty DNS name.', $interface['ip'])); 295 } 296 } 297 298 if (isset($interface['dns'])) { 299 $this->checkDns($interface); 300 } 301 if (isset($interface['ip'])) { 302 $this->checkIp($interface); 303 } 304 if (isset($interface['port']) || $method == 'create') { 305 $this->checkPort($interface); 306 } 307 308 $this->checkBulk($interface); 309 310 if ($update) { 311 $interface = $updInterface; 312 } 313 } 314 unset($interface); 315 316 // check if any of the affected hosts are discovered 317 if ($update) { 318 $interfaces = $this->extendObjects('interface', $interfaces, ['hostid']); 319 320 if ($check_have_items) { 321 $this->checkIfInterfaceHasItems($check_have_items); 322 } 323 } 324 $this->checkValidator(zbx_objectValues($interfaces, 'hostid'), new CHostNormalValidator([ 325 'message' => _('Cannot update interface for discovered host "%1$s".') 326 ])); 327 } 328 329 /** 330 * Add interfaces. 331 * 332 * @param array $interfaces multidimensional array with Interfaces data 333 * 334 * @return array 335 */ 336 public function create(array $interfaces) { 337 $interfaces = zbx_toArray($interfaces); 338 339 $this->checkInput($interfaces, __FUNCTION__); 340 $this->checkMainInterfacesOnCreate($interfaces); 341 342 $interfaceIds = DB::insert('interface', $interfaces); 343 344 return ['interfaceids' => $interfaceIds]; 345 } 346 347 /** 348 * Update interfaces. 349 * 350 * @param array $interfaces multidimensional array with Interfaces data 351 * 352 * @return array 353 */ 354 public function update(array $interfaces) { 355 $interfaces = zbx_toArray($interfaces); 356 357 $this->checkInput($interfaces, __FUNCTION__); 358 $this->checkMainInterfacesOnUpdate($interfaces); 359 360 $data = []; 361 foreach ($interfaces as $interface) { 362 $data[] = [ 363 'values' => $interface, 364 'where' => ['interfaceid' => $interface['interfaceid']] 365 ]; 366 } 367 DB::update('interface', $data); 368 369 return ['interfaceids' => zbx_objectValues($interfaces, 'interfaceid')]; 370 } 371 372 /** 373 * Delete interfaces. 374 * Interface cannot be deleted if it's main interface and exists other interface of same type on same host. 375 * Interface cannot be deleted if it is used in items. 376 * 377 * @param array $interfaceids 378 * 379 * @return array 380 */ 381 public function delete(array $interfaceids) { 382 if (empty($interfaceids)) { 383 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 384 } 385 386 $dbInterfaces = $this->get([ 387 'output' => API_OUTPUT_EXTEND, 388 'interfaceids' => $interfaceids, 389 'editable' => true, 390 'preservekeys' => true 391 ]); 392 foreach ($interfaceids as $interfaceId) { 393 if (!isset($dbInterfaces[$interfaceId])) { 394 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 395 } 396 } 397 398 $this->checkMainInterfacesOnDelete($interfaceids); 399 400 DB::delete('interface', ['interfaceid' => $interfaceids]); 401 402 return ['interfaceids' => $interfaceids]; 403 } 404 405 public function massAdd(array $data) { 406 $interfaces = zbx_toArray($data['interfaces']); 407 $hosts = zbx_toArray($data['hosts']); 408 409 $insertData = []; 410 foreach ($interfaces as $interface) { 411 foreach ($hosts as $host) { 412 $newInterface = $interface; 413 $newInterface['hostid'] = $host['hostid']; 414 415 $insertData[] = $newInterface; 416 } 417 } 418 419 $interfaceIds = $this->create($insertData); 420 421 return ['interfaceids' => $interfaceIds]; 422 } 423 424 protected function validateMassRemove(array $data) { 425 // Check permissions. 426 $this->checkHostPermissions($data['hostids']); 427 428 // Check interfaces. 429 $this->checkValidator($data['hostids'], new CHostNormalValidator([ 430 'message' => _('Cannot delete interface for discovered host "%1$s".') 431 ])); 432 433 foreach ($data['interfaces'] as $interface) { 434 if (!isset($interface['dns']) || !isset($interface['ip']) || !isset($interface['port'])) { 435 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 436 } 437 438 $filter = [ 439 'hostid' => $data['hostids'], 440 'ip' => $interface['ip'], 441 'dns' => $interface['dns'], 442 'port' => $interface['port'] 443 ]; 444 445 if (array_key_exists('bulk', $interface)) { 446 $filter['bulk'] = $interface['bulk']; 447 } 448 449 // check main interfaces 450 $interfacesToRemove = API::getApiService()->select($this->tableName(), [ 451 'output' => ['interfaceid'], 452 'filter' => $filter 453 ]); 454 if ($interfacesToRemove) { 455 $this->checkMainInterfacesOnDelete(zbx_objectValues($interfacesToRemove, 'interfaceid')); 456 } 457 } 458 } 459 460 /** 461 * Remove hosts from interfaces. 462 * 463 * @param array $data 464 * @param array $data['interfaceids'] 465 * @param array $data['hostids'] 466 * @param array $data['templateids'] 467 * 468 * @return array 469 */ 470 public function massRemove(array $data) { 471 $data['interfaces'] = zbx_toArray($data['interfaces']); 472 $data['hostids'] = zbx_toArray($data['hostids']); 473 474 $this->validateMassRemove($data); 475 476 $interfaceIds = []; 477 foreach ($data['interfaces'] as $interface) { 478 $filter = [ 479 'hostid' => $data['hostids'], 480 'ip' => $interface['ip'], 481 'dns' => $interface['dns'], 482 'port' => $interface['port'] 483 ]; 484 if (array_key_exists('bulk', $interface)) { 485 $filter['bulk'] = $interface['bulk']; 486 } 487 488 $interfaces = $this->get([ 489 'output' => ['interfaceid'], 490 'filter' => $filter, 491 'editable' => true, 492 'preservekeys' => true 493 ]); 494 495 if ($interfaces) { 496 $interfaceIds = array_merge($interfaceIds, array_keys($interfaces)); 497 } 498 } 499 500 if ($interfaceIds) { 501 $interfaceIds = array_keys(array_flip($interfaceIds)); 502 DB::delete('interface', ['interfaceid' => $interfaceIds]); 503 } 504 505 return ['interfaceids' => $interfaceIds]; 506 } 507 508 /** 509 * Replace existing interfaces with input interfaces. 510 * 511 * @param array $host 512 */ 513 public function replaceHostInterfaces(array $host) { 514 if (isset($host['interfaces']) && !is_null($host['interfaces'])) { 515 $host['interfaces'] = zbx_toArray($host['interfaces']); 516 517 $this->checkHostInterfaces($host['interfaces'], $host['hostid']); 518 519 $interfacesToDelete = API::HostInterface()->get([ 520 'output' => [], 521 'hostids' => $host['hostid'], 522 'preservekeys' => true, 523 'nopermissions' => true 524 ]); 525 526 $interfacesToAdd = []; 527 $interfacesToUpdate = []; 528 529 foreach ($host['interfaces'] as $interface) { 530 $interface['hostid'] = $host['hostid']; 531 532 if (!isset($interface['interfaceid'])) { 533 $interfacesToAdd[] = $interface; 534 } 535 elseif (isset($interfacesToDelete[$interface['interfaceid']])) { 536 $interfacesToUpdate[] = $interface; 537 unset($interfacesToDelete[$interface['interfaceid']]); 538 } 539 } 540 541 if ($interfacesToUpdate) { 542 API::HostInterface()->checkInput($interfacesToUpdate, 'update'); 543 544 $data = []; 545 foreach ($interfacesToUpdate as $interface) { 546 $data[] = [ 547 'values' => $interface, 548 'where' => ['interfaceid' => $interface['interfaceid']] 549 ]; 550 } 551 DB::update('interface', $data); 552 } 553 554 if ($interfacesToAdd) { 555 $this->checkInput($interfacesToAdd, 'create'); 556 $interfaceids = DB::insert('interface', $interfacesToAdd); 557 558 foreach ($host['interfaces'] as &$interface) { 559 if (!array_key_exists('interfaceid', $interface)) { 560 $interface['interfaceid'] = array_shift($interfaceids); 561 } 562 } 563 unset($interface); 564 } 565 566 if ($interfacesToDelete) { 567 $this->delete(zbx_objectValues($interfacesToDelete, 'interfaceid')); 568 } 569 570 return ['interfaceids' => zbx_objectValues($host['interfaces'], 'interfaceid')]; 571 } 572 573 return ['interfaceids' => []]; 574 } 575 576 /** 577 * Validates the "dns" field. 578 * 579 * @throws APIException if the field is invalid. 580 * 581 * @param array $interface 582 * @param string $interface['dns'] 583 */ 584 protected function checkDns(array $interface) { 585 if ($interface['dns'] === '') { 586 return; 587 } 588 589 $user_macro_parser = new CUserMacroParser(); 590 591 if (!preg_match('/^'.ZBX_PREG_DNS_FORMAT.'$/', $interface['dns']) 592 && $user_macro_parser->parse($interface['dns']) != CParser::PARSE_SUCCESS) { 593 self::exception(ZBX_API_ERROR_PARAMETERS, 594 _s('Incorrect interface DNS parameter "%s" provided.', $interface['dns']) 595 ); 596 } 597 } 598 599 /** 600 * Validates the "ip" field. 601 * 602 * @throws APIException if the field is invalid. 603 * 604 * @param array $interface 605 * @param string $interface['ip'] 606 */ 607 protected function checkIp(array $interface) { 608 if ($interface['ip'] === '') { 609 return; 610 } 611 612 $user_macro_parser = new CUserMacroParser(); 613 614 if (preg_match('/^'.ZBX_PREG_MACRO_NAME_FORMAT.'$/', $interface['ip']) 615 || $user_macro_parser->parse($interface['ip']) == CParser::PARSE_SUCCESS) { 616 return; 617 } 618 619 $ip_parser = new CIPParser(['v6' => ZBX_HAVE_IPV6]); 620 621 if ($ip_parser->parse($interface['ip']) != CParser::PARSE_SUCCESS) { 622 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid IP address "%1$s".', $interface['ip'])); 623 } 624 } 625 626 /** 627 * Validates the "port" field. 628 * 629 * @throws APIException if the field is empty or invalid. 630 * 631 * @param array $interface 632 */ 633 protected function checkPort(array $interface) { 634 if (!isset($interface['port']) || zbx_empty($interface['port'])) { 635 self::exception(ZBX_API_ERROR_PARAMETERS, _('Port cannot be empty for host interface.')); 636 } 637 elseif (!validatePortNumberOrMacro($interface['port'])) { 638 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect interface port "%s" provided.', $interface['port'])); 639 } 640 } 641 642 /** 643 * Checks if the current user has access to the given hosts. Assumes the "hostid" field is valid. 644 * 645 * @throws APIException if the user doesn't have write permissions for the given hosts 646 * 647 * @param array $hostids an array of host IDs 648 */ 649 protected function checkHostPermissions(array $hostids) { 650 if ($hostids) { 651 $hostids = array_unique($hostids); 652 653 $count = API::Host()->get([ 654 'countOutput' => true, 655 'hostids' => $hostids, 656 'editable' => true 657 ]); 658 659 if ($count != count($hostids)) { 660 self::exception(ZBX_API_ERROR_PERMISSIONS, 661 _('No permissions to referred object or it does not exist!') 662 ); 663 } 664 } 665 } 666 667 /** 668 * Validates interface "bulk" field. 669 * For SNMP interfaces bulk value should be either 0 (disabled) or 1 (enabled). 670 * For other non-SNMP interfaces bulk value should be 1 (default). 671 * 672 * @throws APIException if bulk field is incorrect. 673 * 674 * @param array $interface 675 */ 676 protected function checkBulk(array $interface) { 677 if ($interface['type'] !== null && (($interface['type'] != INTERFACE_TYPE_SNMP && isset($interface['bulk']) 678 && $interface['bulk'] != SNMP_BULK_ENABLED) 679 || ($interface['type'] == INTERFACE_TYPE_SNMP && isset($interface['bulk']) 680 && (zbx_empty($interface['bulk']) 681 || ($interface['bulk'] != SNMP_BULK_DISABLED && $interface['bulk'] != SNMP_BULK_ENABLED))))) { 682 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect bulk value for interface.')); 683 } 684 } 685 686 private function checkHostInterfaces(array $interfaces, $hostid) { 687 $interfaces_with_missing_data = []; 688 689 foreach ($interfaces as $interface) { 690 if (array_key_exists('interfaceid', $interface)) { 691 if (!array_key_exists('type', $interface) || !array_key_exists('main', $interface)) { 692 $interfaces_with_missing_data[$interface['interfaceid']] = true; 693 } 694 } 695 elseif (!array_key_exists('type', $interface) || !array_key_exists('main', $interface)) { 696 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 697 } 698 } 699 700 if ($interfaces_with_missing_data) { 701 $dbInterfaces = API::HostInterface()->get([ 702 'output' => ['main', 'type'], 703 'interfaceids' => array_keys($interfaces_with_missing_data), 704 'preservekeys' => true, 705 'nopermissions' => true 706 ]); 707 if (count($interfaces_with_missing_data) != count($dbInterfaces)) { 708 self::exception(ZBX_API_ERROR_PERMISSIONS, 709 _('No permissions to referred object or it does not exist!') 710 ); 711 } 712 } 713 714 foreach ($interfaces as $id => $interface) { 715 if (isset($interface['interfaceid']) && isset($dbInterfaces[$interface['interfaceid']])) { 716 $interfaces[$id] = array_merge($interface, $dbInterfaces[$interface['interfaceid']]); 717 } 718 $interfaces[$id]['hostid'] = $hostid; 719 } 720 721 $this->checkMainInterfaces($interfaces); 722 } 723 724 private function checkMainInterfacesOnCreate(array $interfaces) { 725 $hostIds = []; 726 foreach ($interfaces as $interface) { 727 $hostIds[$interface['hostid']] = $interface['hostid']; 728 } 729 730 $dbInterfaces = API::HostInterface()->get([ 731 'hostids' => $hostIds, 732 'output' => ['hostid', 'main', 'type'], 733 'preservekeys' => true, 734 'nopermissions' => true 735 ]); 736 $interfaces = array_merge($dbInterfaces, $interfaces); 737 738 $this->checkMainInterfaces($interfaces); 739 } 740 741 /** 742 * Prepares data to validate main interface for every interface type. Executes main interface validation. 743 * 744 * @param array $interfaces Array of interfaces to validate. 745 * @param int $interfaces[]['hostid'] Updated interface's hostid. 746 * @param int $interfaces[]['interfaceid'] Updated interface's interfaceid. 747 * 748 * @throws APIException 749 */ 750 private function checkMainInterfacesOnUpdate(array $interfaces) { 751 $hostids = array_keys(array_flip(zbx_objectValues($interfaces, 'hostid'))); 752 753 $dbInterfaces = API::HostInterface()->get([ 754 'hostids' => $hostids, 755 'output' => ['hostid', 'main', 'type'], 756 'preservekeys' => true, 757 'nopermissions' => true 758 ]); 759 760 // update interfaces from DB with data that will be updated. 761 foreach ($interfaces as $interface) { 762 if (isset($dbInterfaces[$interface['interfaceid']])) { 763 $dbInterfaces[$interface['interfaceid']] = array_merge( 764 $dbInterfaces[$interface['interfaceid']], 765 $interface 766 ); 767 } 768 } 769 770 $this->checkMainInterfaces($dbInterfaces); 771 } 772 773 private function checkMainInterfacesOnDelete(array $interfaceIds) { 774 $this->checkIfInterfaceHasItems($interfaceIds); 775 776 $hostids = []; 777 $dbResult = DBselect('SELECT DISTINCT i.hostid FROM interface i WHERE '.dbConditionInt('i.interfaceid', $interfaceIds)); 778 while ($hostData = DBfetch($dbResult)) { 779 $hostids[$hostData['hostid']] = $hostData['hostid']; 780 } 781 782 $interfaces = API::HostInterface()->get([ 783 'hostids' => $hostids, 784 'output' => ['hostid', 'main', 'type'], 785 'preservekeys' => true, 786 'nopermissions' => true 787 ]); 788 $db_interfaces = $interfaces; 789 790 foreach ($interfaceIds as $interfaceId) { 791 unset($interfaces[$interfaceId]); 792 } 793 794 $this->checkMainInterfaces($interfaces, $db_interfaces); 795 } 796 797 /** 798 * Check if main interfaces are correctly set for every interface type. Each host must either have only one main 799 * interface for each interface type, or have no interface of that type at all. If no interfaces are given, it means 800 * the last remaining main interface is trying to be deleted. In that case use $db_interfaces as reference. 801 * 802 * @param array $interfaces Array of interfaces that are created, updated (plus DB) or deleted (plus DB). 803 * @param array $db_interfaces Array of interfaces from DB (used for delete only and if no interfaces are given). 804 */ 805 private function checkMainInterfaces(array $interfaces, array $db_interfaces = []) { 806 if (!$interfaces && $db_interfaces) { 807 $host = API::Host()->get([ 808 'output' => ['name', 'hostid'], 809 'hostids' => zbx_objectValues($db_interfaces, 'hostid'), 810 'preservekeys' => true, 811 'nopermissions' => true 812 ]); 813 $host = reset($host); 814 815 if ($host) { 816 foreach ($db_interfaces as $db_interface) { 817 if (bccomp($db_interface['hostid'], $host['hostid']) == 0) { 818 $type = $db_interface['type']; 819 break; 820 } 821 } 822 823 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 824 'No default interface for "%1$s" type on "%2$s".', hostInterfaceTypeNumToName($type), $host['name'] 825 )); 826 } 827 // Otherwise it's not a host. Could be a Proxy. 828 } 829 830 $interface_count = []; 831 832 if ($db_interfaces) { 833 foreach ($db_interfaces as $db_interface) { 834 $hostid = $db_interface['hostid']; 835 $type = $db_interface['type']; 836 837 if (!array_key_exists($hostid, $interface_count)) { 838 $interface_count[$hostid] = []; 839 } 840 841 if (!array_key_exists($type, $interface_count[$hostid])) { 842 $interface_count[$hostid][$type] = ['main' => 0, 'all' => 0]; 843 } 844 } 845 } 846 847 foreach ($interfaces as $interface) { 848 $hostid = $interface['hostid']; 849 $type = $interface['type']; 850 851 if (!array_key_exists($hostid, $interface_count)) { 852 $interface_count[$hostid] = []; 853 } 854 855 if (!array_key_exists($type, $interface_count[$hostid])) { 856 $interface_count[$hostid][$type] = ['main' => 0, 'all' => 0]; 857 } 858 859 if ($interface['main'] == INTERFACE_PRIMARY) { 860 $interface_count[$hostid][$type]['main']++; 861 } 862 else { 863 $interface_count[$hostid][$type]['all']++; 864 } 865 } 866 867 $main_interface_count = []; 868 $all_interface_count = []; 869 870 foreach ($interface_count as $hostid => $interface_type) { 871 foreach ($interface_type as $type => $counters) { 872 if (!array_key_exists($hostid, $main_interface_count)) { 873 $main_interface_count[$hostid] = 0; 874 } 875 876 $main_interface_count[$hostid] += $counters['main']; 877 878 if (!array_key_exists($hostid, $all_interface_count)) { 879 $all_interface_count[$hostid] = 0; 880 } 881 882 $all_interface_count[$hostid] += $counters['all']; 883 } 884 } 885 886 foreach ($interface_count as $hostid => $interface_type) { 887 foreach ($interface_type as $type => $counters) { 888 if (($counters['all'] > 0 && $counters['main'] == 0) 889 || ($main_interface_count[$hostid] == 0 && $all_interface_count[$hostid] == 0)) { 890 $host = API::Host()->get([ 891 'output' => ['name'], 892 'hostids' => $hostid, 893 'preservekeys' => true, 894 'nopermissions' => true 895 ]); 896 $host = reset($host); 897 898 if ($host) { 899 self::exception(ZBX_API_ERROR_PARAMETERS,_s('No default interface for "%1$s" type on "%2$s".', 900 hostInterfaceTypeNumToName($type), $host['name'] 901 )); 902 } 903 // Otherwise it's not a host. Could be a Proxy. 904 } 905 906 if ($counters['main'] > 1) { 907 self::exception(ZBX_API_ERROR_PARAMETERS, 908 _('Host cannot have more than one default interface of the same type.') 909 ); 910 } 911 } 912 } 913 } 914 915 private function checkIfInterfaceHasItems(array $interfaceIds) { 916 $items = API::Item()->get([ 917 'output' => ['name'], 918 'selectHosts' => ['name'], 919 'interfaceids' => $interfaceIds, 920 'preservekeys' => true, 921 'nopermissions' => true, 922 'limit' => 1 923 ]); 924 925 foreach ($items as $item) { 926 $host = reset($item['hosts']); 927 928 self::exception(ZBX_API_ERROR_PARAMETERS, 929 _s('Interface is linked to item "%1$s" on "%2$s".', $item['name'], $host['name'])); 930 } 931 } 932 933 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 934 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 935 936 if (!$options['countOutput'] && $options['selectHosts'] !== null) { 937 $sqlParts = $this->addQuerySelect('hi.hostid', $sqlParts); 938 } 939 940 return $sqlParts; 941 } 942 943 protected function addRelatedObjects(array $options, array $result) { 944 $result = parent::addRelatedObjects($options, $result); 945 946 $interfaceIds = array_keys($result); 947 948 // adding hosts 949 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 950 $relationMap = $this->createRelationMap($result, 'interfaceid', 'hostid'); 951 $hosts = API::Host()->get([ 952 'output' => $options['selectHosts'], 953 'hosts' => $relationMap->getRelatedIds(), 954 'preservekeys' => true 955 ]); 956 $result = $relationMap->mapMany($result, $hosts, 'hosts'); 957 } 958 959 // adding items 960 if ($options['selectItems'] !== null) { 961 if ($options['selectItems'] != API_OUTPUT_COUNT) { 962 $items = API::Item()->get([ 963 'output' => $this->outputExtend($options['selectItems'], ['itemid', 'interfaceid']), 964 'interfaceids' => $interfaceIds, 965 'nopermissions' => true, 966 'preservekeys' => true, 967 'filter' => ['flags' => null] 968 ]); 969 $relationMap = $this->createRelationMap($items, 'interfaceid', 'itemid'); 970 971 $items = $this->unsetExtraFields($items, ['interfaceid', 'itemid'], $options['selectItems']); 972 $result = $relationMap->mapMany($result, $items, 'items', $options['limitSelects']); 973 } 974 else { 975 $items = API::Item()->get([ 976 'interfaceids' => $interfaceIds, 977 'nopermissions' => true, 978 'filter' => ['flags' => null], 979 'countOutput' => true, 980 'groupCount' => true 981 ]); 982 $items = zbx_toHash($items, 'interfaceid'); 983 foreach ($result as $interfaceid => $interface) { 984 $result[$interfaceid]['items'] = array_key_exists($interfaceid, $items) 985 ? $items[$interfaceid]['rowscount'] 986 : '0'; 987 } 988 } 989 } 990 991 return $result; 992 } 993} 994