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