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 if ($options['groupCount']) { 118 $sqlParts['group']['hostid'] = 'hi.hostid'; 119 } 120 } 121 122 // itemids 123 if (!is_null($options['itemids'])) { 124 zbx_value2array($options['itemids']); 125 126 $sqlParts['from']['items'] = 'items i'; 127 $sqlParts['where'][] = dbConditionInt('i.itemid', $options['itemids']); 128 $sqlParts['where']['hi'] = 'hi.interfaceid=i.interfaceid'; 129 } 130 131 // triggerids 132 if (!is_null($options['triggerids'])) { 133 zbx_value2array($options['triggerids']); 134 135 $sqlParts['from']['functions'] = 'functions f'; 136 $sqlParts['from']['items'] = 'items i'; 137 $sqlParts['where'][] = dbConditionInt('f.triggerid', $options['triggerids']); 138 $sqlParts['where']['hi'] = 'hi.hostid=i.hostid'; 139 $sqlParts['where']['fi'] = 'f.itemid=i.itemid'; 140 } 141 142 // search 143 if (is_array($options['search'])) { 144 zbx_db_search('interface hi', $options, $sqlParts); 145 } 146 147 // filter 148 if (is_array($options['filter'])) { 149 $this->dbFilter('interface hi', $options, $sqlParts); 150 } 151 152 // limit 153 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 154 $sqlParts['limit'] = $options['limit']; 155 } 156 157 if (!$options['countOutput'] && $this->outputIsRequested('details', $options['output'])) { 158 $sqlParts['left_join'][] = ['alias' => 'his', 'table' => 'interface_snmp', 'using' => 'interfaceid']; 159 $sqlParts['left_table'] = ['alias' => $this->tableAlias, 'table' => $this->tableName]; 160 } 161 162 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 163 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 164 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 165 while ($interface = DBfetch($res)) { 166 if ($options['countOutput']) { 167 if ($options['groupCount']) { 168 $result[] = $interface; 169 } 170 else { 171 $result = $interface['rowscount']; 172 } 173 } 174 else { 175 $result[$interface['interfaceid']] = $interface; 176 } 177 } 178 179 if ($options['countOutput']) { 180 return $result; 181 } 182 183 if ($result) { 184 $result = $this->addRelatedObjects($options, $result); 185 $result = $this->unsetExtraFields($result, ['hostid'], $options['output']); 186 } 187 188 // removing keys (hash -> array) 189 if (!$options['preservekeys']) { 190 $result = zbx_cleanHashes($result); 191 } 192 193 // Moving additional fields to separate object. 194 if ($this->outputIsRequested('details', $options['output'])) { 195 foreach ($result as &$value) { 196 $snmp_fields = ['version', 'bulk', 'community', 'securityname', 'securitylevel', 'authpassphrase', 197 'privpassphrase', 'authprotocol', 'privprotocol', 'contextname' 198 ]; 199 200 $interface_type = $value['type']; 201 202 if (!$this->outputIsRequested('type', $options['output'])) { 203 unset($value['type']); 204 } 205 206 $details = []; 207 208 // Handle SNMP related fields. 209 if ($interface_type == INTERFACE_TYPE_SNMP) { 210 foreach ($snmp_fields as $field_name) { 211 $details[$field_name] = $value[$field_name]; 212 unset($value[$field_name]); 213 } 214 215 if ($details['version'] == SNMP_V1 || $details['version'] == SNMP_V2C) { 216 foreach (['securityname', 'securitylevel', 'authpassphrase', 'privpassphrase', 'authprotocol', 217 'privprotocol', 'contextname'] as $snmp_field_name) { 218 unset($details[$snmp_field_name]); 219 } 220 } 221 else { 222 unset($details['community']); 223 } 224 } 225 else { 226 foreach ($snmp_fields as $field_name) { 227 unset($value[$field_name]); 228 } 229 } 230 231 $value['details'] = $details; 232 } 233 unset($value); 234 } 235 236 return $result; 237 } 238 239 /** 240 * Check interfaces input. 241 * 242 * @param array $interfaces 243 * @param string $method 244 */ 245 public function checkInput(array &$interfaces, $method) { 246 $update = ($method == 'update'); 247 248 // permissions 249 if ($update) { 250 $interfaceDBfields = ['interfaceid' => null]; 251 $dbInterfaces = $this->get([ 252 'output' => API_OUTPUT_EXTEND, 253 'interfaceids' => zbx_objectValues($interfaces, 'interfaceid'), 254 'editable' => true, 255 'preservekeys' => true 256 ]); 257 } 258 else { 259 $interfaceDBfields = [ 260 'hostid' => null, 261 'ip' => null, 262 'dns' => null, 263 'useip' => null, 264 'port' => null, 265 'main' => null 266 ]; 267 } 268 269 $dbHosts = API::Host()->get([ 270 'output' => ['host'], 271 'hostids' => zbx_objectValues($interfaces, 'hostid'), 272 'editable' => true, 273 'preservekeys' => true 274 ]); 275 276 $dbProxies = API::Proxy()->get([ 277 'output' => ['host'], 278 'proxyids' => zbx_objectValues($interfaces, 'hostid'), 279 'editable' => true, 280 'preservekeys' => true 281 ]); 282 283 $check_have_items = []; 284 foreach ($interfaces as &$interface) { 285 if (!check_db_fields($interfaceDBfields, $interface)) { 286 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 287 } 288 289 if ($update) { 290 if (!isset($dbInterfaces[$interface['interfaceid']])) { 291 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 292 } 293 294 $dbInterface = $dbInterfaces[$interface['interfaceid']]; 295 if (isset($interface['hostid']) && bccomp($dbInterface['hostid'], $interface['hostid']) != 0) { 296 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Cannot switch host for interface.')); 297 } 298 299 if (array_key_exists('type', $interface) && $interface['type'] != $dbInterface['type']) { 300 $check_have_items[] = $interface['interfaceid']; 301 } 302 303 $interface['hostid'] = $dbInterface['hostid']; 304 305 // we check all fields on "updated" interface 306 $updInterface = $interface; 307 $interface = zbx_array_merge($dbInterface, $interface); 308 } 309 else { 310 if (!isset($dbHosts[$interface['hostid']]) && !isset($dbProxies[$interface['hostid']])) { 311 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 312 } 313 314 if (isset($dbProxies[$interface['hostid']])) { 315 $interface['type'] = INTERFACE_TYPE_UNKNOWN; 316 } 317 elseif (!isset($interface['type'])) { 318 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 319 } 320 } 321 322 if (zbx_empty($interface['ip']) && zbx_empty($interface['dns'])) { 323 self::exception(ZBX_API_ERROR_PARAMETERS, _('IP and DNS cannot be empty for host interface.')); 324 } 325 326 if ($interface['useip'] == INTERFACE_USE_IP && zbx_empty($interface['ip'])) { 327 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Interface with DNS "%1$s" cannot have empty IP address.', $interface['dns'])); 328 } 329 330 if ($interface['useip'] == INTERFACE_USE_DNS && zbx_empty($interface['dns'])) { 331 if ($dbHosts && !empty($dbHosts[$interface['hostid']]['host'])) { 332 self::exception(ZBX_API_ERROR_PARAMETERS, 333 _s('Interface with IP "%1$s" cannot have empty DNS name while having "Use DNS" property on "%2$s".', 334 $interface['ip'], 335 $dbHosts[$interface['hostid']]['host'] 336 )); 337 } 338 elseif ($dbProxies && !empty($dbProxies[$interface['hostid']]['host'])) { 339 self::exception(ZBX_API_ERROR_PARAMETERS, 340 _s('Interface with IP "%1$s" cannot have empty DNS name while having "Use DNS" property on "%2$s".', 341 $interface['ip'], 342 $dbProxies[$interface['hostid']]['host'] 343 )); 344 } 345 else { 346 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Interface with IP "%1$s" cannot have empty DNS name.', $interface['ip'])); 347 } 348 } 349 350 if (isset($interface['dns'])) { 351 $this->checkDns($interface); 352 } 353 if (isset($interface['ip'])) { 354 $this->checkIp($interface); 355 } 356 if (isset($interface['port']) || $method == 'create') { 357 $this->checkPort($interface); 358 } 359 360 if ($update) { 361 $interface = $updInterface; 362 } 363 } 364 unset($interface); 365 366 // check if any of the affected hosts are discovered 367 if ($update) { 368 $interfaces = $this->extendObjects('interface', $interfaces, ['hostid']); 369 370 if ($check_have_items) { 371 $this->checkIfInterfaceHasItems($check_have_items); 372 } 373 } 374 $this->checkValidator(zbx_objectValues($interfaces, 'hostid'), new CHostNormalValidator([ 375 'message' => _('Cannot update interface for discovered host "%1$s".') 376 ])); 377 } 378 379 /** 380 * Check SNMP related inputs. 381 * 382 * @param array $interfaces 383 */ 384 protected function checkSnmpInput(array $interfaces) { 385 foreach ($interfaces as $interface) { 386 if (!array_key_exists('type', $interface) || $interface['type'] != INTERFACE_TYPE_SNMP) { 387 continue; 388 } 389 390 if (!array_key_exists('details', $interface)) { 391 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 392 } 393 394 $this->checkSnmpVersion($interface); 395 396 $this->checkSnmpCommunity($interface); 397 398 $this->checkSnmpBulk($interface); 399 400 $this->checkSnmpSecurityLevel($interface); 401 402 $this->checkSnmpAuthProtocol($interface); 403 404 $this->checkSnmpPrivProtocol($interface); 405 } 406 } 407 408 /** 409 * Sanitize SNMP fields by version. 410 * 411 * @param array $interfaces 412 * 413 * @return array 414 */ 415 protected function sanitizeSnmpFields(array $interfaces): array { 416 $default_fields = [ 417 'community' => '', 418 'securityname' => '', 419 'securitylevel' => DB::getDefault('interface_snmp', 'securitylevel'), 420 'authpassphrase' => '', 421 'privpassphrase' => '', 422 'authprotocol' => DB::getDefault('interface_snmp', 'authprotocol'), 423 'privprotocol' => DB::getDefault('interface_snmp', 'privprotocol'), 424 'contextname' => '' 425 ]; 426 427 foreach ($interfaces as &$interface) { 428 if ($interface['version'] == SNMP_V1 || $interface['version'] == SNMP_V2C) { 429 unset($interface['securityname'], $interface['securitylevel'], $interface['authpassphrase'], 430 $interface['privpassphrase'], $interface['authprotocol'], $interface['privprotocol'], 431 $interface['contextname'] 432 ); 433 } 434 else { 435 unset($interface['community']); 436 } 437 438 $interface = $interface + $default_fields; 439 } 440 441 return $interfaces; 442 } 443 444 /** 445 * Create SNMP interfaces. 446 * 447 * @param array $interfaces 448 */ 449 protected function createSnmpInterfaceDetails(array $interfaces) { 450 if (count($interfaces)) { 451 if (count(array_column($interfaces, 'interfaceid')) != count($interfaces)) { 452 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 453 } 454 455 $interfaces = $this->sanitizeSnmpFields($interfaces); 456 457 foreach ($interfaces as $interface) { 458 DB::insert('interface_snmp', [$interface], false); 459 } 460 } 461 } 462 463 /** 464 * Add interfaces. 465 * 466 * @param array $interfaces multidimensional array with Interfaces data 467 * 468 * @return array 469 */ 470 public function create(array $interfaces) { 471 $interfaces = zbx_toArray($interfaces); 472 473 $this->checkInput($interfaces, __FUNCTION__); 474 $this->checkSnmpInput($interfaces); 475 $this->checkMainInterfacesOnCreate($interfaces); 476 477 $interfaceids = DB::insert('interface', $interfaces); 478 479 $snmp_interfaces = []; 480 foreach ($interfaceids as $key => $id) { 481 if ($interfaces[$key]['type'] == INTERFACE_TYPE_SNMP) { 482 $snmp_interfaces[] = ['interfaceid' => $id] + $interfaces[$key]['details']; 483 } 484 } 485 486 $this->createSnmpInterfaceDetails($snmp_interfaces); 487 488 return ['interfaceids' => $interfaceids]; 489 } 490 491 protected function updateInterfaces(array $interfaces): bool { 492 $data = []; 493 494 foreach ($interfaces as $interface) { 495 $data[] = [ 496 'values' => $interface, 497 'where' => ['interfaceid' => $interface['interfaceid']] 498 ]; 499 } 500 501 DB::update('interface', $data); 502 503 return true; 504 } 505 506 protected function updateInterfaceDetails(array $interfaces): bool { 507 $db_interfaces = $this->get([ 508 'output' => ['type', 'details'], 509 'interfaceids' => array_column($interfaces, 'interfaceid'), 510 'preservekeys' => true 511 ]); 512 DB::delete('interface_snmp', ['interfaceid' => array_column($interfaces, 'interfaceid')]); 513 514 $snmp_interfaces = []; 515 foreach ($interfaces as $interface) { 516 $interfaceid = $interface['interfaceid']; 517 518 // Check new interface type or, if interface type not present, check type from db. 519 if ((!array_key_exists('type', $interface) && $db_interfaces[$interfaceid]['type'] != INTERFACE_TYPE_SNMP) 520 || (array_key_exists('type', $interface) && $interface['type'] != INTERFACE_TYPE_SNMP)) { 521 continue; 522 } 523 else { 524 // Type is required for SNMP validation. 525 $interface['type'] = INTERFACE_TYPE_SNMP; 526 } 527 528 // Merge details with db values or set only values from db. 529 $interface['details'] = array_key_exists('details', $interface) 530 ? $interface['details'] + $db_interfaces[$interfaceid]['details'] 531 : $db_interfaces[$interfaceid]['details']; 532 533 $this->checkSnmpInput([$interface]); 534 535 $snmp_interfaces[] = ['interfaceid' => $interfaceid] + $interface['details']; 536 } 537 538 $this->createSnmpInterfaceDetails($snmp_interfaces); 539 540 return true; 541 } 542 543 /** 544 * Update interfaces. 545 * 546 * @param array $interfaces multidimensional array with Interfaces data 547 * 548 * @return array 549 */ 550 public function update(array $interfaces) { 551 $interfaces = zbx_toArray($interfaces); 552 553 $this->checkInput($interfaces, __FUNCTION__); 554 $this->checkMainInterfacesOnUpdate($interfaces); 555 556 $this->updateInterfaces($interfaces); 557 558 $this->updateInterfaceDetails($interfaces); 559 560 return ['interfaceids' => array_column($interfaces, 'interfaceid')]; 561 } 562 563 /** 564 * Delete interfaces. 565 * Interface cannot be deleted if it's main interface and exists other interface of same type on same host. 566 * Interface cannot be deleted if it is used in items. 567 * 568 * @param array $interfaceids 569 * 570 * @return array 571 */ 572 public function delete(array $interfaceids) { 573 if (empty($interfaceids)) { 574 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 575 } 576 577 $dbInterfaces = $this->get([ 578 'output' => API_OUTPUT_EXTEND, 579 'interfaceids' => $interfaceids, 580 'editable' => true, 581 'preservekeys' => true 582 ]); 583 foreach ($interfaceids as $interfaceId) { 584 if (!isset($dbInterfaces[$interfaceId])) { 585 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 586 } 587 } 588 589 $this->checkMainInterfacesOnDelete($interfaceids); 590 591 DB::delete('interface', ['interfaceid' => $interfaceids]); 592 DB::delete('interface_snmp', ['interfaceid' => $interfaceids]); 593 594 return ['interfaceids' => $interfaceids]; 595 } 596 597 public function massAdd(array $data) { 598 $interfaces = zbx_toArray($data['interfaces']); 599 $hosts = zbx_toArray($data['hosts']); 600 601 $insertData = []; 602 foreach ($interfaces as $interface) { 603 foreach ($hosts as $host) { 604 $newInterface = $interface; 605 $newInterface['hostid'] = $host['hostid']; 606 607 $insertData[] = $newInterface; 608 } 609 } 610 611 $interfaceIds = $this->create($insertData); 612 613 return ['interfaceids' => $interfaceIds]; 614 } 615 616 protected function validateMassRemove(array $data) { 617 if (!$data['hostids'] || !$data['interfaces']) { 618 self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.')); 619 } 620 621 // Check permissions. 622 $this->checkHostPermissions($data['hostids']); 623 624 // Check interfaces. 625 $this->checkValidator($data['hostids'], new CHostNormalValidator([ 626 'message' => _('Cannot delete interface for discovered host "%1$s".') 627 ])); 628 629 foreach ($data['interfaces'] as $interface) { 630 if (!isset($interface['dns']) || !isset($interface['ip']) || !isset($interface['port'])) { 631 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 632 } 633 634 $filter = [ 635 'hostid' => $data['hostids'], 636 'ip' => $interface['ip'], 637 'dns' => $interface['dns'], 638 'port' => $interface['port'] 639 ]; 640 641 // check main interfaces 642 $interfacesToRemove = DB::select($this->tableName(), [ 643 'output' => ['interfaceid'], 644 'filter' => $filter 645 ]); 646 if ($interfacesToRemove) { 647 $this->checkMainInterfacesOnDelete(zbx_objectValues($interfacesToRemove, 'interfaceid')); 648 } 649 } 650 } 651 652 /** 653 * Remove hosts from interfaces. 654 * 655 * @param array $data 656 * @param array $data['interfaceids'] 657 * @param array $data['hostids'] 658 * @param array $data['templateids'] 659 * 660 * @return array 661 */ 662 public function massRemove(array $data) { 663 if (!array_key_exists('hostids', $data) || !array_key_exists('interfaces', $data)) { 664 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 665 } 666 667 $data['interfaces'] = zbx_toArray($data['interfaces']); 668 $data['hostids'] = zbx_toArray($data['hostids']); 669 670 $this->validateMassRemove($data); 671 672 $interfaceIds = []; 673 foreach ($data['interfaces'] as $interface) { 674 $interfaces = $this->get([ 675 'output' => ['interfaceid'], 676 'filter' => [ 677 'hostid' => $data['hostids'], 678 'ip' => $interface['ip'], 679 'dns' => $interface['dns'], 680 'port' => $interface['port'] 681 ], 682 'editable' => true, 683 'preservekeys' => true 684 ]); 685 686 if ($interfaces) { 687 $interfaceIds = array_merge($interfaceIds, array_keys($interfaces)); 688 } 689 } 690 691 if ($interfaceIds) { 692 $interfaceIds = array_keys(array_flip($interfaceIds)); 693 DB::delete('interface', ['interfaceid' => $interfaceIds]); 694 } 695 696 return ['interfaceids' => $interfaceIds]; 697 } 698 699 /** 700 * Replace existing interfaces with input interfaces. 701 * 702 * @param array $host 703 */ 704 public function replaceHostInterfaces(array $host) { 705 if (isset($host['interfaces']) && !is_null($host['interfaces'])) { 706 $host['interfaces'] = zbx_toArray($host['interfaces']); 707 708 $this->checkHostInterfaces($host['interfaces'], $host['hostid']); 709 710 $interfaces_delete = DB::select('interface', [ 711 'output' => [], 712 'filter' => ['hostid' => $host['hostid']], 713 'preservekeys' => true 714 ]); 715 716 $interfaces_add = []; 717 $interfaces_update = []; 718 719 foreach ($host['interfaces'] as $interface) { 720 $interface['hostid'] = $host['hostid']; 721 722 if (!array_key_exists('interfaceid', $interface)) { 723 $interfaces_add[] = $interface; 724 } 725 elseif (array_key_exists($interface['interfaceid'], $interfaces_delete)) { 726 $interfaces_update[] = $interface; 727 unset($interfaces_delete[$interface['interfaceid']]); 728 } 729 } 730 731 if ($interfaces_update) { 732 $this->checkInput($interfaces_update, 'update'); 733 734 $this->updateInterfaces($interfaces_update); 735 736 $this->updateInterfaceDetails($interfaces_update); 737 } 738 739 if ($interfaces_add) { 740 $this->checkInput($interfaces_add, 'create'); 741 $interfaceids = DB::insert('interface', $interfaces_add); 742 743 $this->checkSnmpInput($interfaces_add); 744 745 $snmp_interfaces = []; 746 foreach ($interfaceids as $key => $id) { 747 if ($interfaces_add[$key]['type'] == INTERFACE_TYPE_SNMP) { 748 $snmp_interfaces[] = ['interfaceid' => $id] + $interfaces_add[$key]['details']; 749 } 750 } 751 752 $this->createSnmpInterfaceDetails($snmp_interfaces); 753 754 foreach ($host['interfaces'] as &$interface) { 755 if (!array_key_exists('interfaceid', $interface)) { 756 $interface['interfaceid'] = array_shift($interfaceids); 757 } 758 } 759 unset($interface); 760 } 761 762 if ($interfaces_delete) { 763 $this->delete(array_keys($interfaces_delete)); 764 } 765 766 return ['interfaceids' => array_column($host['interfaces'], 'interfaceid')]; 767 } 768 769 return ['interfaceids' => []]; 770 } 771 772 /** 773 * Validates the "dns" field. 774 * 775 * @throws APIException if the field is invalid. 776 * 777 * @param array $interface 778 * @param string $interface['dns'] 779 */ 780 protected function checkDns(array $interface) { 781 if ($interface['dns'] === '') { 782 return; 783 } 784 785 $user_macro_parser = new CUserMacroParser(); 786 787 if (!preg_match('/^'.ZBX_PREG_DNS_FORMAT.'$/', $interface['dns']) 788 && $user_macro_parser->parse($interface['dns']) != CParser::PARSE_SUCCESS) { 789 self::exception(ZBX_API_ERROR_PARAMETERS, 790 _s('Incorrect interface DNS parameter "%1$s" provided.', $interface['dns']) 791 ); 792 } 793 } 794 795 /** 796 * Validates the "ip" field. 797 * 798 * @throws APIException if the field is invalid. 799 * 800 * @param array $interface 801 * @param string $interface['ip'] 802 */ 803 protected function checkIp(array $interface) { 804 if ($interface['ip'] === '') { 805 return; 806 } 807 808 $user_macro_parser = new CUserMacroParser(); 809 810 if (preg_match('/^'.ZBX_PREG_MACRO_NAME_FORMAT.'$/', $interface['ip']) 811 || $user_macro_parser->parse($interface['ip']) == CParser::PARSE_SUCCESS) { 812 return; 813 } 814 815 $ip_parser = new CIPParser(['v6' => ZBX_HAVE_IPV6]); 816 817 if ($ip_parser->parse($interface['ip']) != CParser::PARSE_SUCCESS) { 818 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid IP address "%1$s".', $interface['ip'])); 819 } 820 } 821 822 /** 823 * Validates the "port" field. 824 * 825 * @throws APIException if the field is empty or invalid. 826 * 827 * @param array $interface 828 */ 829 protected function checkPort(array $interface) { 830 if (!isset($interface['port']) || zbx_empty($interface['port'])) { 831 self::exception(ZBX_API_ERROR_PARAMETERS, _('Port cannot be empty for host interface.')); 832 } 833 elseif (!validatePortNumberOrMacro($interface['port'])) { 834 self::exception(ZBX_API_ERROR_PARAMETERS, 835 _s('Incorrect interface port "%1$s" provided.', $interface['port']) 836 ); 837 } 838 } 839 840 /** 841 * Checks if the current user has access to the given hosts. Assumes the "hostid" field is valid. 842 * 843 * @throws APIException if the user doesn't have write permissions for the given hosts 844 * 845 * @param array $hostids an array of host IDs 846 */ 847 protected function checkHostPermissions(array $hostids) { 848 if ($hostids) { 849 $hostids = array_unique($hostids); 850 851 $count = API::Host()->get([ 852 'countOutput' => true, 853 'hostids' => $hostids, 854 'editable' => true 855 ]); 856 857 if ($count != count($hostids)) { 858 self::exception(ZBX_API_ERROR_PERMISSIONS, 859 _('No permissions to referred object or it does not exist!') 860 ); 861 } 862 } 863 } 864 865 private function checkHostInterfaces(array $interfaces, $hostid) { 866 $interfaces_with_missing_data = []; 867 868 foreach ($interfaces as $interface) { 869 if (array_key_exists('interfaceid', $interface)) { 870 if (!array_key_exists('type', $interface) || !array_key_exists('main', $interface)) { 871 $interfaces_with_missing_data[$interface['interfaceid']] = true; 872 } 873 } 874 elseif (!array_key_exists('type', $interface) || !array_key_exists('main', $interface)) { 875 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 876 } 877 } 878 879 if ($interfaces_with_missing_data) { 880 $dbInterfaces = API::HostInterface()->get([ 881 'output' => ['main', 'type'], 882 'interfaceids' => array_keys($interfaces_with_missing_data), 883 'preservekeys' => true, 884 'nopermissions' => true 885 ]); 886 if (count($interfaces_with_missing_data) != count($dbInterfaces)) { 887 self::exception(ZBX_API_ERROR_PERMISSIONS, 888 _('No permissions to referred object or it does not exist!') 889 ); 890 } 891 } 892 893 foreach ($interfaces as $id => $interface) { 894 if (isset($interface['interfaceid']) && isset($dbInterfaces[$interface['interfaceid']])) { 895 $interfaces[$id] = array_merge($interface, $dbInterfaces[$interface['interfaceid']]); 896 } 897 $interfaces[$id]['hostid'] = $hostid; 898 } 899 900 $this->checkMainInterfaces($interfaces); 901 } 902 903 private function checkMainInterfacesOnCreate(array $interfaces) { 904 $hostIds = []; 905 foreach ($interfaces as $interface) { 906 $hostIds[$interface['hostid']] = $interface['hostid']; 907 } 908 909 $dbInterfaces = API::HostInterface()->get([ 910 'hostids' => $hostIds, 911 'output' => ['hostid', 'main', 'type'], 912 'preservekeys' => true, 913 'nopermissions' => true 914 ]); 915 $interfaces = array_merge($dbInterfaces, $interfaces); 916 917 $this->checkMainInterfaces($interfaces); 918 } 919 920 /** 921 * Prepares data to validate main interface for every interface type. Executes main interface validation. 922 * 923 * @param array $interfaces Array of interfaces to validate. 924 * @param int $interfaces[]['hostid'] Updated interface's hostid. 925 * @param int $interfaces[]['interfaceid'] Updated interface's interfaceid. 926 * 927 * @throws APIException 928 */ 929 private function checkMainInterfacesOnUpdate(array $interfaces) { 930 $hostids = array_keys(array_flip(zbx_objectValues($interfaces, 'hostid'))); 931 932 $dbInterfaces = API::HostInterface()->get([ 933 'hostids' => $hostids, 934 'output' => ['hostid', 'main', 'type'], 935 'preservekeys' => true, 936 'nopermissions' => true 937 ]); 938 939 // update interfaces from DB with data that will be updated. 940 foreach ($interfaces as $interface) { 941 if (isset($dbInterfaces[$interface['interfaceid']])) { 942 $dbInterfaces[$interface['interfaceid']] = array_merge( 943 $dbInterfaces[$interface['interfaceid']], 944 $interface 945 ); 946 } 947 } 948 949 $this->checkMainInterfaces($dbInterfaces); 950 } 951 952 private function checkMainInterfacesOnDelete(array $interfaceIds) { 953 $this->checkIfInterfaceHasItems($interfaceIds); 954 955 $hostids = []; 956 $dbResult = DBselect('SELECT DISTINCT i.hostid FROM interface i WHERE '.dbConditionInt('i.interfaceid', $interfaceIds)); 957 while ($hostData = DBfetch($dbResult)) { 958 $hostids[$hostData['hostid']] = $hostData['hostid']; 959 } 960 961 $interfaces = API::HostInterface()->get([ 962 'hostids' => $hostids, 963 'output' => ['hostid', 'main', 'type'], 964 'preservekeys' => true, 965 'nopermissions' => true 966 ]); 967 $db_interfaces = $interfaces; 968 969 foreach ($interfaceIds as $interfaceId) { 970 unset($interfaces[$interfaceId]); 971 } 972 973 $this->checkMainInterfaces($interfaces, $db_interfaces); 974 } 975 976 /** 977 * Check if main interfaces are correctly set for every interface type. Each host must either have only one main 978 * interface for each interface type, or have no interface of that type at all. If no interfaces are given, it means 979 * the last remaining main interface is trying to be deleted. In that case use $db_interfaces as reference. 980 * 981 * @param array $interfaces Array of interfaces that are created, updated (plus DB) or deleted (plus DB). 982 * @param array $db_interfaces Array of interfaces from DB (used for delete only and if no interfaces are given). 983 */ 984 private function checkMainInterfaces(array $interfaces, array $db_interfaces = []) { 985 if (!$interfaces && $db_interfaces) { 986 $host = API::Host()->get([ 987 'output' => ['name', 'hostid'], 988 'hostids' => zbx_objectValues($db_interfaces, 'hostid'), 989 'preservekeys' => true, 990 'nopermissions' => true 991 ]); 992 $host = reset($host); 993 994 if ($host) { 995 foreach ($db_interfaces as $db_interface) { 996 if (bccomp($db_interface['hostid'], $host['hostid']) == 0) { 997 $type = $db_interface['type']; 998 break; 999 } 1000 } 1001 1002 self::exception(ZBX_API_ERROR_PARAMETERS, _s( 1003 'No default interface for "%1$s" type on "%2$s".', hostInterfaceTypeNumToName($type), $host['name'] 1004 )); 1005 } 1006 // Otherwise it's not a host. Could be a Proxy. 1007 } 1008 1009 $interface_count = []; 1010 1011 if ($db_interfaces) { 1012 foreach ($db_interfaces as $db_interface) { 1013 $hostid = $db_interface['hostid']; 1014 $type = $db_interface['type']; 1015 1016 if (!array_key_exists($hostid, $interface_count)) { 1017 $interface_count[$hostid] = []; 1018 } 1019 1020 if (!array_key_exists($type, $interface_count[$hostid])) { 1021 $interface_count[$hostid][$type] = ['main' => 0, 'all' => 0]; 1022 } 1023 } 1024 } 1025 1026 foreach ($interfaces as $interface) { 1027 $hostid = $interface['hostid']; 1028 $type = $interface['type']; 1029 1030 if (!array_key_exists($hostid, $interface_count)) { 1031 $interface_count[$hostid] = []; 1032 } 1033 1034 if (!array_key_exists($type, $interface_count[$hostid])) { 1035 $interface_count[$hostid][$type] = ['main' => 0, 'all' => 0]; 1036 } 1037 1038 if ($interface['main'] == INTERFACE_PRIMARY) { 1039 $interface_count[$hostid][$type]['main']++; 1040 } 1041 else { 1042 $interface_count[$hostid][$type]['all']++; 1043 } 1044 } 1045 1046 $main_interface_count = []; 1047 $all_interface_count = []; 1048 1049 foreach ($interface_count as $hostid => $interface_type) { 1050 foreach ($interface_type as $type => $counters) { 1051 if (!array_key_exists($hostid, $main_interface_count)) { 1052 $main_interface_count[$hostid] = 0; 1053 } 1054 1055 $main_interface_count[$hostid] += $counters['main']; 1056 1057 if (!array_key_exists($hostid, $all_interface_count)) { 1058 $all_interface_count[$hostid] = 0; 1059 } 1060 1061 $all_interface_count[$hostid] += $counters['all']; 1062 } 1063 } 1064 1065 foreach ($interface_count as $hostid => $interface_type) { 1066 foreach ($interface_type as $type => $counters) { 1067 if (($counters['all'] > 0 && $counters['main'] == 0) 1068 || ($main_interface_count[$hostid] == 0 && $all_interface_count[$hostid] == 0)) { 1069 $host = API::Host()->get([ 1070 'output' => ['name'], 1071 'hostids' => $hostid, 1072 'preservekeys' => true, 1073 'nopermissions' => true 1074 ]); 1075 $host = reset($host); 1076 1077 if ($host) { 1078 self::exception(ZBX_API_ERROR_PARAMETERS,_s('No default interface for "%1$s" type on "%2$s".', 1079 hostInterfaceTypeNumToName($type), $host['name'] 1080 )); 1081 } 1082 // Otherwise it's not a host. Could be a Proxy. 1083 } 1084 1085 if ($counters['main'] > 1) { 1086 self::exception(ZBX_API_ERROR_PARAMETERS, 1087 _('Host cannot have more than one default interface of the same type.') 1088 ); 1089 } 1090 } 1091 } 1092 } 1093 1094 private function checkIfInterfaceHasItems(array $interfaceIds) { 1095 $items = API::Item()->get([ 1096 'output' => ['name'], 1097 'selectHosts' => ['name'], 1098 'interfaceids' => $interfaceIds, 1099 'preservekeys' => true, 1100 'nopermissions' => true, 1101 'limit' => 1 1102 ]); 1103 1104 foreach ($items as $item) { 1105 $host = reset($item['hosts']); 1106 1107 self::exception(ZBX_API_ERROR_PARAMETERS, 1108 _s('Interface is linked to item "%1$s" on "%2$s".', $item['name'], $host['name'])); 1109 } 1110 } 1111 1112 /** 1113 * Check if SNMP version is valid. Valid versions: SNMP_V1, SNMP_V2C, SNMP_V3. 1114 * 1115 * @param array $interface 1116 * 1117 * @throws APIException if "version" value is incorrect. 1118 */ 1119 protected function checkSnmpVersion(array $interface) { 1120 if (!array_key_exists('version', $interface['details']) 1121 || !in_array($interface['details']['version'], [SNMP_V1, SNMP_V2C, SNMP_V3])) { 1122 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1123 } 1124 } 1125 1126 /** 1127 * Check SNMP community. For SNMPv1 and SNMPv2c it required. 1128 * 1129 * @param array $interface 1130 * 1131 * @throws APIException if "community" value is incorrect. 1132 */ 1133 protected function checkSnmpCommunity(array $interface) { 1134 if (($interface['details']['version'] == SNMP_V1 || $interface['details']['version'] == SNMP_V2C) 1135 && (!array_key_exists('community', $interface['details']) 1136 || $interface['details']['community'] === '')) { 1137 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1138 } 1139 } 1140 1141 /** 1142 * Validates SNMP interface "bulk" field. 1143 * 1144 * @param array $interface 1145 * 1146 * @throws APIException if "bulk" value is incorrect. 1147 */ 1148 protected function checkSnmpBulk(array $interface) { 1149 if ($interface['type'] !== null && (($interface['type'] != INTERFACE_TYPE_SNMP 1150 && isset($interface['details']['bulk']) && $interface['details']['bulk'] != SNMP_BULK_ENABLED) 1151 || ($interface['type'] == INTERFACE_TYPE_SNMP && isset($interface['details']['bulk']) 1152 && (zbx_empty($interface['details']['bulk']) 1153 || ($interface['details']['bulk'] != SNMP_BULK_DISABLED 1154 && $interface['details']['bulk'] != SNMP_BULK_ENABLED))))) { 1155 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect bulk value for interface.')); 1156 } 1157 } 1158 1159 /** 1160 * Check SNMP Security level field. 1161 * 1162 * @param array $interface 1163 * @param array $interface['details'] 1164 * @param array $interface['details']['version'] SNMP version 1165 * @param array $interface['details']['securitylevel'] SNMP security level 1166 * 1167 * @throws APIException if "securitylevel" value is incorrect. 1168 */ 1169 protected function checkSnmpSecurityLevel(array $interface) { 1170 if ($interface['details']['version'] == SNMP_V3 && (array_key_exists('securitylevel', $interface['details']) 1171 && !in_array($interface['details']['securitylevel'], [ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV, 1172 ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV, ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV]))) { 1173 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1174 } 1175 } 1176 1177 /** 1178 * Check SNMP authentication protocol. 1179 * 1180 * @param array $interface 1181 * @param array $interface['details'] 1182 * @param array $interface['details']['version'] SNMP version 1183 * @param array $interface['details']['authprotocol'] SNMP authentication protocol 1184 * 1185 * @throws APIException if "authprotocol" value is incorrect. 1186 */ 1187 protected function checkSnmpAuthProtocol(array $interface) { 1188 if ($interface['details']['version'] == SNMP_V3 && (array_key_exists('authprotocol', $interface['details']) 1189 && !in_array($interface['details']['authprotocol'], [ITEM_AUTHPROTOCOL_MD5, 1190 ITEM_AUTHPROTOCOL_SHA]))) { 1191 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1192 } 1193 } 1194 1195 /** 1196 * Check SNMP Privacy protocol. 1197 * 1198 * @param array $interface 1199 * @param array $interface['details'] 1200 * @param array $interface['details']['version'] SNMP version 1201 * @param array $interface['details']['privprotocol'] SNMP privacy protocol 1202 * 1203 * @throws APIException if "privprotocol" value is incorrect. 1204 */ 1205 protected function checkSnmpPrivProtocol(array $interface) { 1206 if ($interface['details']['version'] == SNMP_V3 && (array_key_exists('privprotocol', $interface['details']) 1207 && !in_array($interface['details']['privprotocol'], [ITEM_PRIVPROTOCOL_DES, ITEM_PRIVPROTOCOL_AES]))) { 1208 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.')); 1209 } 1210 } 1211 1212 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 1213 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 1214 1215 if (!$options['countOutput'] && $this->outputIsRequested('details', $options['output'])) { 1216 // Select interface type to check show details array or not. 1217 $sqlParts = $this->addQuerySelect('hi.type', $sqlParts); 1218 1219 $sqlParts = $this->addQuerySelect(dbConditionCoalesce('his.version', SNMP_V2C, 'version'), $sqlParts); 1220 $sqlParts = $this->addQuerySelect(dbConditionCoalesce('his.bulk', SNMP_BULK_ENABLED, 'bulk'), $sqlParts); 1221 $sqlParts = $this->addQuerySelect(dbConditionCoalesce('his.community', '', 'community'), $sqlParts); 1222 $sqlParts = $this->addQuerySelect(dbConditionCoalesce('his.securityname', '', 'securityname'), $sqlParts); 1223 $sqlParts = $this->addQuerySelect( 1224 dbConditionCoalesce('his.securitylevel', ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV, 'securitylevel'), 1225 $sqlParts 1226 ); 1227 $sqlParts = $this->addQuerySelect( 1228 dbConditionCoalesce('his.authpassphrase', '', 'authpassphrase'), 1229 $sqlParts 1230 ); 1231 $sqlParts = $this->addQuerySelect( 1232 dbConditionCoalesce('his.privpassphrase', '', 'privpassphrase'), 1233 $sqlParts 1234 ); 1235 $sqlParts = $this->addQuerySelect( 1236 dbConditionCoalesce('his.authprotocol', ITEM_AUTHPROTOCOL_MD5, 'authprotocol'), 1237 $sqlParts 1238 ); 1239 $sqlParts = $this->addQuerySelect( 1240 dbConditionCoalesce('his.privprotocol', ITEM_PRIVPROTOCOL_DES, 'privprotocol'), 1241 $sqlParts 1242 ); 1243 $sqlParts = $this->addQuerySelect(dbConditionCoalesce('his.contextname', '', 'contextname'), $sqlParts); 1244 } 1245 1246 if (!$options['countOutput'] && $options['selectHosts'] !== null) { 1247 $sqlParts = $this->addQuerySelect('hi.hostid', $sqlParts); 1248 } 1249 1250 return $sqlParts; 1251 } 1252 1253 protected function addRelatedObjects(array $options, array $result) { 1254 $result = parent::addRelatedObjects($options, $result); 1255 1256 $interfaceIds = array_keys($result); 1257 1258 // adding hosts 1259 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 1260 $relationMap = $this->createRelationMap($result, 'interfaceid', 'hostid'); 1261 $hosts = API::Host()->get([ 1262 'output' => $options['selectHosts'], 1263 'hosts' => $relationMap->getRelatedIds(), 1264 'preservekeys' => true 1265 ]); 1266 $result = $relationMap->mapMany($result, $hosts, 'hosts'); 1267 } 1268 1269 // adding items 1270 if ($options['selectItems'] !== null) { 1271 if ($options['selectItems'] != API_OUTPUT_COUNT) { 1272 $items = API::Item()->get([ 1273 'output' => $this->outputExtend($options['selectItems'], ['itemid', 'interfaceid']), 1274 'interfaceids' => $interfaceIds, 1275 'nopermissions' => true, 1276 'preservekeys' => true, 1277 'filter' => ['flags' => null] 1278 ]); 1279 $relationMap = $this->createRelationMap($items, 'interfaceid', 'itemid'); 1280 1281 $items = $this->unsetExtraFields($items, ['interfaceid', 'itemid'], $options['selectItems']); 1282 $result = $relationMap->mapMany($result, $items, 'items', $options['limitSelects']); 1283 } 1284 else { 1285 $items = API::Item()->get([ 1286 'interfaceids' => $interfaceIds, 1287 'nopermissions' => true, 1288 'filter' => ['flags' => null], 1289 'countOutput' => true, 1290 'groupCount' => true 1291 ]); 1292 $items = zbx_toHash($items, 'interfaceid'); 1293 foreach ($result as $interfaceid => $interface) { 1294 $result[$interfaceid]['items'] = array_key_exists($interfaceid, $items) 1295 ? $items[$interfaceid]['rowscount'] 1296 : '0'; 1297 } 1298 } 1299 } 1300 1301 return $result; 1302 } 1303} 1304