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 proxies. 24 */ 25class CProxy extends CApiService { 26 27 public const ACCESS_RULES = [ 28 'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER], 29 'create' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 30 'update' => ['min_user_type' => USER_TYPE_SUPER_ADMIN], 31 'delete' => ['min_user_type' => USER_TYPE_SUPER_ADMIN] 32 ]; 33 34 protected $tableName = 'hosts'; 35 protected $tableAlias = 'h'; 36 protected $sortColumns = ['hostid', 'host', 'status']; 37 38 /** 39 * Get proxy data. 40 * 41 * @param array $options 42 * @param array $options['proxyids'] 43 * @param bool $options['editable'] only with read-write permission. Ignored for SuperAdmins 44 * @param int $options['count'] returns value in rowscount 45 * @param string $options['pattern'] 46 * @param int $options['limit'] 47 * @param string $options['sortfield'] 48 * @param string $options['sortorder'] 49 * 50 * @return array 51 */ 52 public function get($options = []) { 53 $result = []; 54 55 $sqlParts = [ 56 'select' => ['hostid' => 'h.hostid'], 57 'from' => ['hosts' => 'hosts h'], 58 'where' => ['h.status IN ('.HOST_STATUS_PROXY_ACTIVE.','.HOST_STATUS_PROXY_PASSIVE.')'], 59 'order' => [], 60 'limit' => null 61 ]; 62 63 $defOptions = [ 64 'proxyids' => 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 'countOutput' => false, 77 'preservekeys' => false, 78 'selectHosts' => null, 79 'selectInterface' => null, 80 'sortfield' => '', 81 'sortorder' => '', 82 'limit' => null 83 ]; 84 $options = zbx_array_merge($defOptions, $options); 85 86 // editable + PERMISSION CHECK 87 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) { 88 $permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ; 89 if ($permission == PERM_READ_WRITE) { 90 return []; 91 } 92 } 93 94 // proxyids 95 if (!is_null($options['proxyids'])) { 96 zbx_value2array($options['proxyids']); 97 $sqlParts['where'][] = dbConditionInt('h.hostid', $options['proxyids']); 98 } 99 100 // filter 101 if (is_array($options['filter'])) { 102 $this->dbFilter('hosts h', $options, $sqlParts); 103 } 104 105 // search 106 if (is_array($options['search'])) { 107 zbx_db_search('hosts h', $options, $sqlParts); 108 } 109 110 // output 111 if ($options['output'] == API_OUTPUT_EXTEND) { 112 $sqlParts['select']['hostid'] = 'h.hostid'; 113 $sqlParts['select']['host'] = 'h.host'; 114 $sqlParts['select']['status'] = 'h.status'; 115 $sqlParts['select']['lastaccess'] = 'h.lastaccess'; 116 } 117 118 // countOutput 119 if ($options['countOutput']) { 120 $options['sortfield'] = ''; 121 $sqlParts['select'] = ['COUNT(DISTINCT h.hostid) AS rowscount']; 122 } 123 124 // limit 125 if (zbx_ctype_digit($options['limit']) && $options['limit']) { 126 $sqlParts['limit'] = $options['limit']; 127 } 128 129 /* 130 * Cleaning the output from write-only properties. 131 */ 132 if ($options['output'] === API_OUTPUT_EXTEND) { 133 $options['output'] = array_diff(array_keys(DB::getSchema($this->tableName())['fields']), 134 ['tls_psk_identity', 'tls_psk'] 135 ); 136 } 137 /* 138 * For internal calls of API method, is possible to get the write-only fields if they were specified in output. 139 * Specify write-only fields in output only if they will not appear in debug mode. 140 */ 141 elseif (is_array($options['output']) && APP::getMode() === APP::EXEC_MODE_API) { 142 $options['output'] = array_diff($options['output'], ['tls_psk_identity', 'tls_psk']); 143 } 144 145 $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 146 $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); 147 $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); 148 while ($proxy = DBfetch($res)) { 149 if ($options['countOutput']) { 150 $result = $proxy['rowscount']; 151 } 152 else { 153 $proxy['proxyid'] = $proxy['hostid']; 154 unset($proxy['hostid']); 155 156 $result[$proxy['proxyid']] = $proxy; 157 } 158 } 159 160 if ($options['countOutput']) { 161 return $result; 162 } 163 164 if ($result) { 165 $result = $this->addRelatedObjects($options, $result); 166 $result = $this->unsetExtraFields($result, ['hostid'], $options['output']); 167 } 168 169 // removing keys (hash -> array) 170 if (!$options['preservekeys']) { 171 $result = zbx_cleanHashes($result); 172 } 173 174 return $result; 175 } 176 177 /** 178 * Create proxy. 179 * 180 * @param array $proxies 181 * 182 * @return array 183 */ 184 public function create(array $proxies) { 185 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 186 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 187 } 188 189 $proxies = zbx_toArray($proxies); 190 191 $this->validateCreate($proxies); 192 193 foreach ($proxies as &$proxy) { 194 // Clean encryption fields. 195 if ($proxy['status'] == HOST_STATUS_PROXY_PASSIVE) { 196 if (!array_key_exists('tls_connect', $proxy)) { 197 $proxy['tls_psk_identity'] = ''; 198 $proxy['tls_psk'] = ''; 199 $proxy['tls_issuer'] = ''; 200 $proxy['tls_subject'] = ''; 201 } 202 else { 203 if ($proxy['tls_connect'] != HOST_ENCRYPTION_PSK) { 204 $proxy['tls_psk_identity'] = ''; 205 $proxy['tls_psk'] = ''; 206 } 207 208 if ($proxy['tls_connect'] != HOST_ENCRYPTION_CERTIFICATE) { 209 $proxy['tls_issuer'] = ''; 210 $proxy['tls_subject'] = ''; 211 } 212 } 213 } 214 elseif ($proxy['status'] == HOST_STATUS_PROXY_ACTIVE) { 215 if (!array_key_exists('tls_accept', $proxy)) { 216 $proxy['tls_psk_identity'] = ''; 217 $proxy['tls_psk'] = ''; 218 $proxy['tls_issuer'] = ''; 219 $proxy['tls_subject'] = ''; 220 } 221 else { 222 if (($proxy['tls_accept'] & HOST_ENCRYPTION_PSK) != HOST_ENCRYPTION_PSK) { 223 $proxy['tls_psk_identity'] = ''; 224 $proxy['tls_psk'] = ''; 225 } 226 227 if (($proxy['tls_accept'] & HOST_ENCRYPTION_CERTIFICATE) != HOST_ENCRYPTION_CERTIFICATE) { 228 $proxy['tls_issuer'] = ''; 229 $proxy['tls_subject'] = ''; 230 } 231 } 232 } 233 234 // Mark the interface as main to pass host interface validation. 235 if ($proxy['status'] == HOST_STATUS_PROXY_PASSIVE && array_key_exists('interface', $proxy)) { 236 $proxy['interface']['main'] = INTERFACE_PRIMARY; 237 } 238 } 239 unset($proxy); 240 241 $proxyids = DB::insert('hosts', $proxies); 242 243 $hostUpdate = []; 244 foreach ($proxies as $key => $proxy) { 245 if (!empty($proxy['hosts'])) { 246 $hostUpdate[] = [ 247 'values' => ['proxy_hostid' => $proxyids[$key]], 248 'where' => ['hostid' => zbx_objectValues($proxy['hosts'], 'hostid')] 249 ]; 250 } 251 252 // create interface 253 if ($proxy['status'] == HOST_STATUS_PROXY_PASSIVE) { 254 $proxy['interface']['hostid'] = $proxyids[$key]; 255 256 if (!API::HostInterface()->create($proxy['interface'])) { 257 self::exception(ZBX_API_ERROR_INTERNAL, _('Proxy interface creation failed.')); 258 } 259 } 260 } 261 262 DB::update('hosts', $hostUpdate); 263 264 return ['proxyids' => $proxyids]; 265 } 266 267 /** 268 * Update proxy. 269 * 270 * @param array $proxies 271 * 272 * @return array 273 */ 274 public function update(array $proxies) { 275 if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) { 276 self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!')); 277 } 278 279 $proxies = zbx_toArray($proxies); 280 281 $proxyids = zbx_objectValues($proxies, 'proxyid'); 282 283 foreach ($proxies as &$proxy) { 284 if (array_key_exists('proxyid', $proxy)) { 285 $proxy['hostid'] = $proxy['proxyid']; 286 } 287 elseif (array_key_exists('hostid', $proxy)) { 288 $proxy['proxyid'] = $proxy['hostid']; 289 } 290 } 291 unset($proxy); 292 293 $db_proxies = $this->get([ 294 'output' => ['proxyid', 'hostid', 'host', 'status', 'tls_connect', 'tls_accept', 'tls_issuer', 295 'tls_subject' 296 ], 297 'proxyids' => $proxyids, 298 'editable' => true, 299 'preservekeys' => true 300 ]); 301 302 // Load existing values of PSK fields of proxies independently from APP mode. 303 $proxies_psk_fields = DB::select($this->tableName(), [ 304 'output' => ['tls_psk_identity', 'tls_psk'], 305 'hostids' => array_keys($db_proxies), 306 'preservekeys' => true 307 ]); 308 309 foreach ($proxies_psk_fields as $hostid => $psk_fields) { 310 $db_proxies[$hostid] += $psk_fields; 311 } 312 313 $this->validateUpdate($proxies, $db_proxies); 314 315 foreach ($proxies as &$proxy) { 316 $status = array_key_exists('status', $proxy) ? $proxy['status'] : $db_proxies[$proxy['proxyid']]['status']; 317 318 // Clean encryption fields. 319 $tls_connect = array_key_exists('tls_connect', $proxy) 320 ? $proxy['tls_connect'] 321 : $db_proxies[$proxy['proxyid']]['tls_connect']; 322 323 $tls_accept = array_key_exists('tls_accept', $proxy) 324 ? $proxy['tls_accept'] 325 : $db_proxies[$proxy['proxyid']]['tls_accept']; 326 327 // Clean PSK fields. 328 if ($tls_connect != HOST_ENCRYPTION_PSK && ($tls_accept & HOST_ENCRYPTION_PSK) != HOST_ENCRYPTION_PSK) { 329 $proxy['tls_psk_identity'] = ''; 330 $proxy['tls_psk'] = ''; 331 } 332 333 // Clean certificate fields. 334 if ($tls_connect != HOST_ENCRYPTION_CERTIFICATE 335 && ($tls_accept & HOST_ENCRYPTION_CERTIFICATE) != HOST_ENCRYPTION_CERTIFICATE) { 336 $proxy['tls_issuer'] = ''; 337 $proxy['tls_subject'] = ''; 338 } 339 340 // Mark the interface as main to pass host interface validation. 341 if ($status == HOST_STATUS_PROXY_PASSIVE && array_key_exists('interface', $proxy)) { 342 $proxy['interface']['main'] = INTERFACE_PRIMARY; 343 } 344 345 // Clean proxy address field. 346 if ($status == HOST_STATUS_PROXY_PASSIVE && !array_key_exists('proxy_address', $proxy)) { 347 $proxy['proxy_address'] = ''; 348 } 349 } 350 unset($proxy); 351 352 $proxyUpdate = []; 353 $hostUpdate = []; 354 355 foreach ($proxies as $proxy) { 356 $proxyUpdate[] = [ 357 'values' => $proxy, 358 'where' => ['hostid' => $proxy['proxyid']] 359 ]; 360 361 if (isset($proxy['hosts'])) { 362 // unset proxy for all hosts except for discovered hosts 363 $hostUpdate[] = [ 364 'values' => ['proxy_hostid' => 0], 365 'where' => [ 366 'proxy_hostid' => $proxy['proxyid'], 367 'flags' => ZBX_FLAG_DISCOVERY_NORMAL 368 ] 369 ]; 370 371 $hostUpdate[] = [ 372 'values' => ['proxy_hostid' => $proxy['proxyid']], 373 'where' => ['hostid' => zbx_objectValues($proxy['hosts'], 'hostid')] 374 ]; 375 } 376 377 if (array_key_exists('status', $proxy) && $proxy['status'] == HOST_STATUS_PROXY_ACTIVE) { 378 // If this is an active proxy, delete it's interface. 379 380 $interfaces = API::HostInterface()->get([ 381 'hostids' => $proxy['hostid'], 382 'output' => ['interfaceid'] 383 ]); 384 $interfaceIds = zbx_objectValues($interfaces, 'interfaceid'); 385 386 if ($interfaceIds) { 387 API::HostInterface()->delete($interfaceIds); 388 } 389 } 390 elseif (array_key_exists('interface', $proxy) && is_array($proxy['interface'])) { 391 // Update the interface of a passive proxy. 392 393 $proxy['interface']['hostid'] = $proxy['hostid']; 394 395 $result = isset($proxy['interface']['interfaceid']) 396 ? API::HostInterface()->update($proxy['interface']) 397 : API::HostInterface()->create($proxy['interface']); 398 399 if (!$result) { 400 self::exception(ZBX_API_ERROR_INTERNAL, _('Proxy interface update failed.')); 401 } 402 } 403 } 404 405 DB::update('hosts', $proxyUpdate); 406 DB::update('hosts', $hostUpdate); 407 408 return ['proxyids' => $proxyids]; 409 } 410 411 /** 412 * @param array $proxyids 413 * 414 * @return array 415 */ 416 public function delete(array $proxyids) { 417 $this->validateDelete($proxyids, $db_proxies); 418 419 DB::delete('interface', ['hostid' => $proxyids]); 420 DB::delete('hosts', ['hostid' => $proxyids]); 421 422 $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_PROXY, $db_proxies); 423 424 return ['proxyids' => $proxyids]; 425 } 426 427 /** 428 * @param array $proxyids 429 * @param array $db_proxies 430 * 431 * @throws APIException if the input is invalid. 432 */ 433 private function validateDelete(array &$proxyids, array &$db_proxies = null) { 434 $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true]; 435 if (!CApiInputValidator::validate($api_input_rules, $proxyids, '/', $error)) { 436 self::exception(ZBX_API_ERROR_PARAMETERS, $error); 437 } 438 439 $db_proxies = $this->get([ 440 'output' => ['proxyid', 'host'], 441 'proxyids' => $proxyids, 442 'editable' => true, 443 'preservekeys' => true 444 ]); 445 446 foreach ($proxyids as $proxyid) { 447 if (!array_key_exists($proxyid, $db_proxies)) { 448 self::exception(ZBX_API_ERROR_PERMISSIONS, 449 _('No permissions to referred object or it does not exist!') 450 ); 451 } 452 } 453 454 $this->checkUsedInDiscovery($db_proxies); 455 $this->checkUsedInHosts($db_proxies); 456 $this->checkUsedInActions($db_proxies); 457 } 458 459 /** 460 * Check if proxy is used in network discovery rule. 461 * 462 * @param array $proxies 463 * @param string $proxies[<proxyid>]['host'] 464 */ 465 private function checkUsedInDiscovery(array $proxies) { 466 $db_drules = DB::select('drules', [ 467 'output' => ['proxy_hostid', 'name'], 468 'filter' => ['proxy_hostid' => array_keys($proxies)], 469 'limit' => 1 470 ]); 471 472 if ($db_drules) { 473 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Proxy "%1$s" is used by discovery rule "%2$s".', 474 $proxies[$db_drules[0]['proxy_hostid']]['host'], $db_drules[0]['name'] 475 )); 476 } 477 } 478 479 /** 480 * Check if proxy is used to monitor hosts. 481 * 482 * @param array $proxies 483 * @param string $proxies[<proxyid>]['host'] 484 */ 485 protected function checkUsedInHosts(array $proxies) { 486 $db_hosts = DB::select('hosts', [ 487 'output' => ['proxy_hostid', 'name'], 488 'filter' => ['proxy_hostid' => array_keys($proxies)], 489 'limit' => 1 490 ]); 491 492 if ($db_hosts) { 493 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host "%1$s" is monitored with proxy "%2$s".', 494 $db_hosts[0]['name'], $proxies[$db_hosts[0]['proxy_hostid']]['host'] 495 )); 496 } 497 } 498 499 /** 500 * Check if proxy is used in actions. 501 * 502 * @param array $proxies 503 * @param string $proxies[<proxyid>]['host'] 504 */ 505 private function checkUsedInActions(array $proxies) { 506 $db_actions = DBfetchArray(DBselect( 507 'SELECT a.name,c.value AS proxy_hostid'. 508 ' FROM actions a,conditions c'. 509 ' WHERE a.actionid=c.actionid'. 510 ' AND c.conditiontype='.CONDITION_TYPE_PROXY. 511 ' AND '.dbConditionString('c.value', array_keys($proxies)), 512 1 513 )); 514 515 if ($db_actions) { 516 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Proxy "%1$s" is used by action "%2$s".', 517 $proxies[$db_actions[0]['proxy_hostid']]['host'], $db_actions[0]['name'] 518 )); 519 } 520 } 521 522 protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) { 523 $sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts); 524 525 if (!$options['countOutput'] && $options['selectInterface'] !== null) { 526 $sqlParts = $this->addQuerySelect('h.hostid', $sqlParts); 527 } 528 529 return $sqlParts; 530 } 531 532 protected function addRelatedObjects(array $options, array $result) { 533 $result = parent::addRelatedObjects($options, $result); 534 535 $proxyIds = array_keys($result); 536 537 // selectHosts 538 if ($options['selectHosts'] !== null && $options['selectHosts'] != API_OUTPUT_COUNT) { 539 $hosts = API::Host()->get([ 540 'output' => $this->outputExtend($options['selectHosts'], ['hostid', 'proxy_hostid']), 541 'proxyids' => $proxyIds, 542 'preservekeys' => true 543 ]); 544 545 $relationMap = $this->createRelationMap($hosts, 'proxy_hostid', 'hostid'); 546 $hosts = $this->unsetExtraFields($hosts, ['proxy_hostid', 'hostid'], $options['selectHosts']); 547 $result = $relationMap->mapMany($result, $hosts, 'hosts'); 548 } 549 550 // adding host interface 551 if ($options['selectInterface'] !== null && $options['selectInterface'] != API_OUTPUT_COUNT) { 552 $interfaces = API::HostInterface()->get([ 553 'output' => $this->outputExtend($options['selectInterface'], ['interfaceid', 'hostid']), 554 'hostids' => $proxyIds, 555 'nopermissions' => true, 556 'preservekeys' => true 557 ]); 558 559 $relationMap = $this->createRelationMap($interfaces, 'hostid', 'interfaceid'); 560 $interfaces = $this->unsetExtraFields($interfaces, ['hostid', 'interfaceid'], $options['selectInterface']); 561 $result = $relationMap->mapOne($result, $interfaces, 'interface'); 562 563 foreach ($result as $key => $proxy) { 564 if (!empty($proxy['interface'])) { 565 $result[$key]['interface'] = $proxy['interface']; 566 } 567 } 568 } 569 570 return $result; 571 } 572 573 /** 574 * Validate connections from/to proxy and PSK fields. 575 * 576 * @param array $proxies proxies data array 577 * 578 * @throws APIException if incorrect encryption options. 579 */ 580 protected function validateEncryption(array $proxies) { 581 foreach ($proxies as $proxy) { 582 $available_connect_types = [HOST_ENCRYPTION_NONE, HOST_ENCRYPTION_PSK, HOST_ENCRYPTION_CERTIFICATE]; 583 $available_accept_types = [ 584 HOST_ENCRYPTION_NONE, HOST_ENCRYPTION_PSK, (HOST_ENCRYPTION_NONE | HOST_ENCRYPTION_PSK), 585 HOST_ENCRYPTION_CERTIFICATE, (HOST_ENCRYPTION_NONE | HOST_ENCRYPTION_CERTIFICATE), 586 (HOST_ENCRYPTION_PSK | HOST_ENCRYPTION_CERTIFICATE), 587 (HOST_ENCRYPTION_NONE | HOST_ENCRYPTION_PSK | HOST_ENCRYPTION_CERTIFICATE) 588 ]; 589 590 if ($proxy['status'] == HOST_STATUS_PROXY_PASSIVE && array_key_exists('tls_connect', $proxy) 591 && !in_array($proxy['tls_connect'], $available_connect_types)) { 592 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_connect', 593 _s('unexpected value "%1$s"', $proxy['tls_connect']) 594 )); 595 } 596 597 if ($proxy['status'] == HOST_STATUS_PROXY_ACTIVE && array_key_exists('tls_accept', $proxy) 598 && !in_array($proxy['tls_accept'], $available_accept_types)) { 599 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'tls_accept', 600 _s('unexpected value "%1$s"', $proxy['tls_accept']) 601 )); 602 } 603 604 // PSK validation. 605 if ((array_key_exists('tls_connect', $proxy) && $proxy['tls_connect'] == HOST_ENCRYPTION_PSK 606 && $proxy['status'] == HOST_STATUS_PROXY_PASSIVE) 607 || (array_key_exists('tls_accept', $proxy) 608 && ($proxy['tls_accept'] & HOST_ENCRYPTION_PSK) == HOST_ENCRYPTION_PSK 609 && $proxy['status'] == HOST_STATUS_PROXY_ACTIVE)) { 610 if (!array_key_exists('tls_psk_identity', $proxy) || zbx_empty($proxy['tls_psk_identity'])) { 611 self::exception(ZBX_API_ERROR_PARAMETERS, 612 _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk_identity', _('cannot be empty')) 613 ); 614 } 615 616 if (!array_key_exists('tls_psk', $proxy) || zbx_empty($proxy['tls_psk'])) { 617 self::exception(ZBX_API_ERROR_PARAMETERS, 618 _s('Incorrect value for field "%1$s": %2$s.', 'tls_psk', _('cannot be empty')) 619 ); 620 } 621 622 if (!preg_match('/^([0-9a-f]{2})+$/i', $proxy['tls_psk'])) { 623 self::exception(ZBX_API_ERROR_PARAMETERS, _( 624 'Incorrect value used for PSK field. It should consist of an even number of hexadecimal characters.' 625 )); 626 } 627 628 if (strlen($proxy['tls_psk']) < PSK_MIN_LEN) { 629 self::exception(ZBX_API_ERROR_PARAMETERS, 630 _s('PSK is too short. Minimum is %1$s hex-digits.', PSK_MIN_LEN) 631 ); 632 } 633 } 634 } 635 } 636 637 /** 638 * Validates the input parameters for the create() method. 639 * 640 * @param array $proxies proxies data array 641 * 642 * @throws APIException if the input is invalid. 643 */ 644 protected function validateCreate(array $proxies) { 645 $proxy_db_fields = ['host' => null, 'status' => null]; 646 $names = []; 647 648 $ip_range_parser = new CIPRangeParser(['v6' => ZBX_HAVE_IPV6, 'ranges' => false]); 649 $host_name_parser = new CHostNameParser(); 650 651 foreach ($proxies as $proxy) { 652 if (!check_db_fields($proxy_db_fields, $proxy)) { 653 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 654 } 655 656 if ($host_name_parser->parse($proxy['host']) != CParser::PARSE_SUCCESS) { 657 self::exception(ZBX_API_ERROR_PARAMETERS, 658 _s('Incorrect characters used for proxy name "%1$s".', $proxy['host']) 659 ); 660 } 661 662 $names[$proxy['host']] = true; 663 } 664 665 $proxy_exists = $this->get([ 666 'output' => ['proxyid', 'host'], 667 'filter' => ['host' => array_keys($names)], 668 'limit' => 1 669 ]); 670 671 if ($proxy_exists) { 672 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Proxy "%1$s" already exists.', $proxy_exists[0]['host'])); 673 } 674 675 $hostids = []; 676 677 foreach ($proxies as $proxy) { 678 if ($proxy['status'] != HOST_STATUS_PROXY_ACTIVE && $proxy['status'] != HOST_STATUS_PROXY_PASSIVE) { 679 self::exception(ZBX_API_ERROR_PARAMETERS, 680 _s('Incorrect value used for proxy status "%1$s".', $proxy['status']) 681 ); 682 } 683 684 // interface 685 if ($proxy['status'] == HOST_STATUS_PROXY_PASSIVE 686 && (!array_key_exists('interface', $proxy) 687 || !is_array($proxy['interface']) || !$proxy['interface'])) { 688 self::exception(ZBX_API_ERROR_PARAMETERS, 689 _s('No interface provided for proxy "%1$s".', $proxy['host']) 690 ); 691 } 692 693 if (array_key_exists('proxy_address', $proxy)) { 694 switch ($proxy['status']) { 695 case HOST_STATUS_PROXY_PASSIVE: 696 if ($proxy['proxy_address'] !== '') { 697 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 698 'proxy_address', _('should be empty') 699 )); 700 } 701 break; 702 703 case HOST_STATUS_PROXY_ACTIVE: 704 if ($proxy['proxy_address'] !== '' && !$ip_range_parser->parse($proxy['proxy_address'])) { 705 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 706 'proxy_address', $ip_range_parser->getError() 707 )); 708 } 709 break; 710 } 711 } 712 713 if (array_key_exists('hosts', $proxy) && $proxy['hosts']) { 714 $hostids = array_merge($hostids, zbx_objectValues($proxy['hosts'], 'hostid')); 715 } 716 717 // Property 'auto_compress' is read-only. 718 if (array_key_exists('auto_compress', $proxy)) { 719 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 720 } 721 } 722 723 if ($hostids) { 724 // Check if host exists. 725 $hosts = API::Host()->get([ 726 'output' => ['hostid'], 727 'hostids' => $hostids, 728 'editable' => true 729 ]); 730 731 if (!$hosts) { 732 self::exception(ZBX_API_ERROR_PARAMETERS, 733 _('No permissions to referred object or it does not exist!') 734 ); 735 } 736 737 // Check if any of the affected hosts are discovered. 738 $this->checkValidator($hostids, new CHostNormalValidator([ 739 'message' => _('Cannot update proxy for discovered host "%1$s".') 740 ])); 741 } 742 743 $this->validateEncryption($proxies); 744 } 745 746 /** 747 * Validates the input parameters for the update() method. 748 * 749 * @param array $proxies proxies data array 750 * @param array $db_proxies db proxies data array 751 * 752 * @throws APIException if the input is invalid. 753 */ 754 protected function validateUpdate(array $proxies, array $db_proxies) { 755 $proxy_db_fields = ['proxyid' => null]; 756 $names = []; 757 758 $ip_range_parser = new CIPRangeParser(['v6' => ZBX_HAVE_IPV6, 'ranges' => false]); 759 $host_name_parser = new CHostNameParser(); 760 761 foreach ($proxies as $proxy) { 762 if (!check_db_fields($proxy_db_fields, $proxy)) { 763 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 764 } 765 766 if (!array_key_exists($proxy['proxyid'], $db_proxies)) { 767 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!')); 768 } 769 770 // host 771 if (array_key_exists('host', $proxy)) { 772 if ($host_name_parser->parse($proxy['host']) != CParser::PARSE_SUCCESS) { 773 self::exception(ZBX_API_ERROR_PARAMETERS, 774 _s('Incorrect characters used for proxy name "%1$s".', $proxy['host']) 775 ); 776 } 777 778 if ($proxy['host'] !== $db_proxies[$proxy['proxyid']]['host']) { 779 $names[$proxy['host']] = true; 780 } 781 } 782 783 // Property 'auto_compress' is read-only. 784 if (array_key_exists('auto_compress', $proxy)) { 785 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect input parameters.')); 786 } 787 } 788 789 // Check names that have been changed. 790 if ($names) { 791 $proxies_exists = $this->get([ 792 'output' => ['proxyid'], 793 'filter' => ['host' => array_keys($names)] 794 ]); 795 796 foreach ($proxies as $proxy) { 797 if (array_key_exists('host', $proxy) && $proxy['host'] !== $db_proxies[$proxy['proxyid']]['host']) { 798 foreach ($proxies_exists as $proxy_exists) { 799 if (bccomp($proxy_exists['proxyid'], $proxy['proxyid']) != 0) { 800 self::exception(ZBX_API_ERROR_PARAMETERS, 801 _s('Proxy "%1$s" already exists.', $proxy['host']) 802 ); 803 } 804 } 805 } 806 } 807 } 808 809 $hostids = []; 810 811 foreach ($proxies as $proxy) { 812 if (array_key_exists('status', $proxy) && ($proxy['status'] != HOST_STATUS_PROXY_ACTIVE 813 && $proxy['status'] != HOST_STATUS_PROXY_PASSIVE)) { 814 self::exception(ZBX_API_ERROR_PARAMETERS, 815 _s('Incorrect value used for proxy status "%1$s".', $proxy['status']) 816 ); 817 } 818 819 if (array_key_exists('proxy_address', $proxy)) { 820 switch (array_key_exists('status', $proxy) ? $proxy['status'] : $db_proxy['status']) { 821 case HOST_STATUS_PROXY_PASSIVE: 822 if ($proxy['proxy_address'] !== '') { 823 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 824 'proxy_address', _('should be empty') 825 )); 826 } 827 break; 828 829 case HOST_STATUS_PROXY_ACTIVE: 830 if ($proxy['proxy_address'] !== '' && !$ip_range_parser->parse($proxy['proxy_address'])) { 831 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 832 'proxy_address', $ip_range_parser->getError() 833 )); 834 } 835 break; 836 } 837 } 838 839 if (array_key_exists('hosts', $proxy) && $proxy['hosts']) { 840 $hostids = array_merge($hostids, zbx_objectValues($proxy['hosts'], 'hostid')); 841 } 842 } 843 844 if ($hostids) { 845 // Check if host exists. 846 $hosts = API::Host()->get([ 847 'output' => ['hostid'], 848 'hostids' => $hostids, 849 'editable' => true 850 ]); 851 852 if (!$hosts) { 853 self::exception(ZBX_API_ERROR_PARAMETERS, 854 _('No permissions to referred object or it does not exist!') 855 ); 856 } 857 858 // Check if any of the affected hosts are discovered. 859 $this->checkValidator($hostids, new CHostNormalValidator([ 860 'message' => _('Cannot update proxy for discovered host "%1$s".') 861 ])); 862 } 863 864 $status = array_key_exists('status', $proxy) ? $proxy['status'] : $db_proxies[$proxy['proxyid']]['status']; 865 866 // interface 867 if ($status == HOST_STATUS_PROXY_PASSIVE && array_key_exists('interface', $proxy) 868 && (!is_array($proxy['interface']) || !$proxy['interface'])) { 869 self::exception(ZBX_API_ERROR_PARAMETERS, _s('No interface provided for proxy "%1$s".', $proxy['host'])); 870 } 871 872 $proxies = $this->extendFromObjects(zbx_toHash($proxies, 'proxyid'), $db_proxies, [ 873 'status', 'tls_connect', 'tls_accept', 'tls_psk_identity', 'tls_psk' 874 ]); 875 876 $this->validateEncryption($proxies); 877 } 878} 879