"Fossies" - the Fresh Open Source Software Archive

Member "zabbix-4.0.1/frontends/php/include/classes/api/services/CDRule.php" (29 Oct 2018, 28159 Bytes) of package /linux/misc/zabbix-4.0.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "CDRule.php" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 3.4.14_vs_4.0.0rc1.

    1 <?php
    2 /*
    3 ** Zabbix
    4 ** Copyright (C) 2001-2018 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 discovery rules.
   24  */
   25 class CDRule extends CApiService {
   26 
   27     protected $tableName = 'drules';
   28     protected $tableAlias = 'dr';
   29     protected $sortColumns = ['druleid', 'name'];
   30 
   31     /**
   32      * Get drule data.
   33      *
   34      * @param array $options
   35      *
   36      * @return array
   37      */
   38     public function get(array $options = []) {
   39         $result = [];
   40 
   41         $sqlParts = [
   42             'select'    => ['drules' => 'dr.druleid'],
   43             'from'      => ['drules' => 'drules dr'],
   44             'where'     => [],
   45             'group'     => [],
   46             'order'     => [],
   47             'limit'     => null
   48         ];
   49 
   50         $defOptions = [
   51             'druleids'                  => null,
   52             'dhostids'                  => null,
   53             'dserviceids'               => null,
   54             'editable'                  => false,
   55             'selectDHosts'              => null,
   56             'selectDChecks'             => null,
   57             // filter
   58             'filter'                    => null,
   59             'search'                    => null,
   60             'searchByAny'               => null,
   61             'startSearch'               => false,
   62             'excludeSearch'             => false,
   63             'searchWildcardsEnabled'    => null,
   64             // output
   65             'output'                    => API_OUTPUT_EXTEND,
   66             'countOutput'               => false,
   67             'groupCount'                => false,
   68             'preservekeys'              => false,
   69             'sortfield'                 => '',
   70             'sortorder'                 => '',
   71             'limit'                     => null,
   72             'limitSelects'              => null
   73         ];
   74         $options = zbx_array_merge($defOptions, $options);
   75 
   76         if (self::$userData['type'] < USER_TYPE_ZABBIX_ADMIN) {
   77             return [];
   78         }
   79 
   80 // druleids
   81         if (!is_null($options['druleids'])) {
   82             zbx_value2array($options['druleids']);
   83             $sqlParts['where']['druleid'] = dbConditionInt('dr.druleid', $options['druleids']);
   84         }
   85 
   86 // dhostids
   87         if (!is_null($options['dhostids'])) {
   88             zbx_value2array($options['dhostids']);
   89 
   90             $sqlParts['from']['dhosts'] = 'dhosts dh';
   91             $sqlParts['where']['dhostid'] = dbConditionInt('dh.dhostid', $options['dhostids']);
   92             $sqlParts['where']['dhdr'] = 'dh.druleid=dr.druleid';
   93 
   94             if ($options['groupCount']) {
   95                 $sqlParts['group']['dhostid'] = 'dh.dhostid';
   96             }
   97         }
   98 
   99 // dserviceids
  100         if (!is_null($options['dserviceids'])) {
  101             zbx_value2array($options['dserviceids']);
  102 
  103             $sqlParts['from']['dhosts'] = 'dhosts dh';
  104             $sqlParts['from']['dservices'] = 'dservices ds';
  105 
  106             $sqlParts['where']['dserviceid'] = dbConditionInt('ds.dserviceid', $options['dserviceids']);
  107             $sqlParts['where']['dhdr'] = 'dh.druleid=dr.druleid';
  108             $sqlParts['where']['dhds'] = 'dh.dhostid=ds.dhostid';
  109 
  110             if ($options['groupCount']) {
  111                 $sqlParts['group']['dserviceid'] = 'ds.dserviceid';
  112             }
  113         }
  114 
  115 // search
  116         if (!is_null($options['search'])) {
  117             zbx_db_search('drules dr', $options, $sqlParts);
  118         }
  119 
  120 // filter
  121         if (is_array($options['filter'])) {
  122             if (array_key_exists('delay', $options['filter']) && $options['filter']['delay'] !== null) {
  123                 $options['filter']['delay'] = getTimeUnitFilters($options['filter']['delay']);
  124             }
  125 
  126             $this->dbFilter('drules dr', $options, $sqlParts);
  127         }
  128 
  129 // search
  130         if (is_array($options['search'])) {
  131             zbx_db_search('drules dr', $options, $sqlParts);
  132         }
  133 
  134 // limit
  135         if (zbx_ctype_digit($options['limit']) && $options['limit']) {
  136             $sqlParts['limit'] = $options['limit'];
  137         }
  138 //------------
  139 
  140         $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
  141         $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
  142         $dbRes = DBselect($this->createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
  143         while ($drule = DBfetch($dbRes)) {
  144             if ($options['countOutput']) {
  145                 if ($options['groupCount']) {
  146                     $result[] = $drule;
  147                 }
  148                 else {
  149                     $result = $drule['rowscount'];
  150                 }
  151             }
  152             else {
  153                 $result[$drule['druleid']] = $drule;
  154             }
  155         }
  156 
  157         if ($options['countOutput']) {
  158             return $result;
  159         }
  160 
  161         if ($result) {
  162             $result = $this->addRelatedObjects($options, $result);
  163         }
  164 
  165 // removing keys (hash -> array)
  166         if (!$options['preservekeys']) {
  167             $result = zbx_cleanHashes($result);
  168         }
  169 
  170     return $result;
  171     }
  172 
  173     /**
  174      * Validate the input parameters for create() method.
  175      *
  176      * @param array $drules     Discovery rules data.
  177      *
  178      * @throws APIException if the input is invalid.
  179      */
  180     protected function validateCreate(array $drules) {
  181         // Check permissions.
  182         if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) {
  183             self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
  184         }
  185 
  186         if (!$drules) {
  187             self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
  188         }
  189 
  190         $proxy_hostids = [];
  191 
  192         $ip_range_parser = new CIPRangeParser(['v6' => ZBX_HAVE_IPV6, 'dns' => false, 'max_ipv4_cidr' => 30]);
  193 
  194         foreach ($drules as $drule) {
  195             if (!array_key_exists('name', $drule)) {
  196                 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Field "%1$s" is mandatory.', 'name'));
  197             }
  198             elseif (is_array($drule['name'])) {
  199                 self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
  200             }
  201             elseif ($drule['name'] === '' || $drule['name'] === null || $drule['name'] === false) {
  202                 self::exception(ZBX_API_ERROR_PARAMETERS,
  203                     _s('Incorrect value for field "%1$s": %2$s.', 'name', _('cannot be empty'))
  204                 );
  205             }
  206 
  207             if (!array_key_exists('iprange', $drule) || $drule['iprange'] === '') {
  208                 self::exception(ZBX_API_ERROR_PARAMETERS,
  209                     _s('Incorrect value for field "%1$s": %2$s.', 'iprange', _('cannot be empty'))
  210                 );
  211             }
  212             elseif (!$ip_range_parser->parse($drule['iprange'])) {
  213                 self::exception(ZBX_API_ERROR_PARAMETERS,
  214                     _s('Incorrect value for field "%1$s": %2$s.', 'iprange', $ip_range_parser->getError())
  215                 );
  216             }
  217             elseif (bccomp($ip_range_parser->getMaxIPCount(), ZBX_DISCOVERER_IPRANGE_LIMIT) > 0) {
  218                 self::exception(ZBX_API_ERROR_PARAMETERS,
  219                     _s('Incorrect value for field "%1$s": %2$s.', 'iprange',
  220                         _s('IP range "%1$s" exceeds "%2$s" address limit', $ip_range_parser->getMaxIPRange(),
  221                             ZBX_DISCOVERER_IPRANGE_LIMIT
  222                         )
  223                     )
  224                 );
  225             }
  226 
  227             if (array_key_exists('delay', $drule)
  228                     && !validateTimeUnit($drule['delay'], 1, SEC_PER_WEEK, false, $error, ['usermacros' => true])) {
  229                 self::exception(ZBX_API_ERROR_PARAMETERS,
  230                     _s('Incorrect value for field "%1$s": %2$s.', 'delay', $error)
  231                 );
  232             }
  233 
  234             if (array_key_exists('status', $drule) && $drule['status'] != DRULE_STATUS_DISABLED
  235                     && $drule['status'] != DRULE_STATUS_ACTIVE) {
  236                 self::exception(ZBX_API_ERROR_PARAMETERS,
  237                     _s('Incorrect value "%1$s" for "%2$s" field.', $drule['status'], 'status')
  238                 );
  239             }
  240 
  241             if (array_key_exists('proxy_hostid', $drule)) {
  242                 if (!zbx_is_int($drule['proxy_hostid'])) {
  243                     self::exception(ZBX_API_ERROR_PARAMETERS,
  244                         _s('Incorrect value "%1$s" for "%2$s" field.', $drule['proxy_hostid'], 'proxy_hostid')
  245                     );
  246                 }
  247 
  248                 if ($drule['proxy_hostid'] > 0) {
  249                     $proxy_hostids[] = $drule['proxy_hostid'];
  250                 }
  251             }
  252 
  253             if (array_key_exists('dchecks', $drule) && $drule['dchecks']) {
  254                 $this->validateDChecks($drule['dchecks']);
  255             }
  256             else {
  257                 self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot save discovery rule without checks.'));
  258             }
  259         }
  260 
  261         // Check drule name duplicates in input data.
  262         $duplicate = CArrayHelper::findDuplicate($drules, 'name');
  263         if ($duplicate) {
  264             self::exception(ZBX_API_ERROR_PARAMETERS,
  265                 _s('Discovery rule "%1$s" already exists.', $duplicate['name'])
  266             );
  267         }
  268 
  269         // Check drule name duplicates in DB.
  270         $db_duplicate = $this->get([
  271             'output' => ['name'],
  272             'filter' => ['name' => zbx_objectValues($drules, 'name')],
  273             'limit' => 1
  274         ]);
  275 
  276         if ($db_duplicate) {
  277             self::exception(ZBX_API_ERROR_PARAMETERS,
  278                 _s('Discovery rule "%1$s" already exists.', $db_duplicate[0]['name'])
  279             );
  280         }
  281 
  282         // Check proxy IDs.
  283         if ($proxy_hostids) {
  284             $db_proxies = API::proxy()->get([
  285                 'output' => ['proxyid'],
  286                 'proxyids' => $proxy_hostids,
  287                 'preservekeys' => true
  288             ]);
  289             foreach ($proxy_hostids as $proxy_hostid) {
  290                 if (!array_key_exists($proxy_hostid, $db_proxies)) {
  291                     self::exception(ZBX_API_ERROR_PARAMETERS,
  292                         _s('Incorrect value "%1$s" for "%2$s" field.', $proxy_hostid, 'proxy_hostid')
  293                     );
  294                 }
  295             }
  296         }
  297     }
  298 
  299     /**
  300      * Validate the input parameters for update() method.
  301      *
  302      * @param array $drules         Discovery rules data.
  303      *
  304      * @throws APIException if the input is invalid.
  305      */
  306     protected function validateUpdate(array $drules) {
  307         // Check permissions.
  308         if (self::$userData['type'] == USER_TYPE_ZABBIX_USER) {
  309             self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
  310         }
  311 
  312         if (!$drules) {
  313             self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
  314         }
  315 
  316         // Validate given IDs.
  317         $this->checkObjectIds($drules, 'druleid',
  318             _('Field "%1$s" is mandatory.'),
  319             _s('Incorrect value for field "%1$s": %2$s.', 'druleid', _('cannot be empty')),
  320             _s('Incorrect value for field "%1$s": %2$s.', 'druleid', _('a numeric value is expected'))
  321         );
  322 
  323         $db_drules = $this->get([
  324             'output' => ['druleid', 'name'],
  325             'druleids' => zbx_objectValues($drules, 'druleid'),
  326             'preservekeys' => true
  327         ]);
  328 
  329         $drule_names_changed = [];
  330         $proxy_hostids = [];
  331 
  332         $ip_range_parser = new CIPRangeParser(['v6' => ZBX_HAVE_IPV6, 'dns' => false, 'max_ipv4_cidr' => 30]);
  333 
  334         foreach ($drules as $drule) {
  335             if (!array_key_exists($drule['druleid'], $db_drules)) {
  336                 self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
  337             }
  338 
  339             if (array_key_exists('name', $drule)) {
  340                 if (is_array($drule['name'])) {
  341                     self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
  342                 }
  343                 elseif ($drule['name'] === '' || $drule['name'] === null || $drule['name'] === false) {
  344                     self::exception(ZBX_API_ERROR_PARAMETERS,
  345                         _s('Incorrect value for field "%1$s": %2$s.', 'name', _('cannot be empty'))
  346                     );
  347                 }
  348 
  349                 if ($db_drules[$drule['druleid']]['name'] !== $drule['name']) {
  350                     $drule_names_changed[] = $drule;
  351                 }
  352             }
  353 
  354             if (array_key_exists('iprange', $drule)) {
  355                 if ($drule['iprange'] === '') {
  356                     self::exception(ZBX_API_ERROR_PARAMETERS,
  357                         _s('Incorrect value for field "%1$s": %2$s.', 'iprange', _('cannot be empty'))
  358                     );
  359                 }
  360                 elseif (!$ip_range_parser->parse($drule['iprange'])) {
  361                     self::exception(ZBX_API_ERROR_PARAMETERS,
  362                         _s('Incorrect value for field "%1$s": %2$s.', 'iprange', $ip_range_parser->getError())
  363                     );
  364                 }
  365                 elseif (bccomp($ip_range_parser->getMaxIPCount(), ZBX_DISCOVERER_IPRANGE_LIMIT) > 0) {
  366                     self::exception(ZBX_API_ERROR_PARAMETERS,
  367                         _s('Incorrect value for field "%1$s": %2$s.', 'iprange',
  368                             _s('IP range "%1$s" exceeds "%2$s" address limit', $ip_range_parser->getMaxIPRange(),
  369                                 ZBX_DISCOVERER_IPRANGE_LIMIT
  370                             )
  371                         )
  372                     );
  373                 }
  374             }
  375 
  376             if (array_key_exists('delay', $drule)
  377                     && !validateTimeUnit($drule['delay'], 1, SEC_PER_WEEK, false, $error, ['usermacros' => true])) {
  378                 self::exception(ZBX_API_ERROR_PARAMETERS,
  379                     _s('Incorrect value for field "%1$s": %2$s.', 'delay', $error)
  380                 );
  381             }
  382 
  383             if (array_key_exists('status', $drule) && $drule['status'] != DRULE_STATUS_DISABLED
  384                     && $drule['status'] != DRULE_STATUS_ACTIVE) {
  385                 self::exception(ZBX_API_ERROR_PARAMETERS,
  386                     _s('Incorrect value "%1$s" for "%2$s" field.', $drule['status'], 'status')
  387                 );
  388             }
  389 
  390             if (array_key_exists('proxy_hostid', $drule)) {
  391                 if (!zbx_is_int($drule['proxy_hostid'])) {
  392                     self::exception(ZBX_API_ERROR_PARAMETERS,
  393                         _s('Incorrect value "%1$s" for "%2$s" field.', $drule['proxy_hostid'], 'proxy_hostid')
  394                     );
  395                 }
  396 
  397                 if ($drule['proxy_hostid'] > 0) {
  398                     $proxy_hostids[] = $drule['proxy_hostid'];
  399                 }
  400             }
  401 
  402             if (array_key_exists('dchecks', $drule)) {
  403                 if ($drule['dchecks']) {
  404                     $this->validateDChecks($drule['dchecks']);
  405                 }
  406                 else {
  407                     self::exception(ZBX_API_ERROR_PARAMETERS, _('Cannot save discovery rule without checks.'));
  408                 }
  409             }
  410         }
  411 
  412         if ($drule_names_changed) {
  413             // Check drule name duplicates in input data.
  414             $duplicate = CArrayHelper::findDuplicate($drule_names_changed, 'name');
  415             if ($duplicate) {
  416                 self::exception(ZBX_API_ERROR_PARAMETERS,
  417                     _s('Discovery rule "%1$s" already exists.', $duplicate['name'])
  418                 );
  419             }
  420 
  421             // Check drule name duplicates in DB.
  422             $db_duplicate = $this->get([
  423                 'output' => ['name'],
  424                 'filter' => ['name' => zbx_objectValues($drule_names_changed, 'name')],
  425                 'limit' => 1
  426             ]);
  427 
  428             if ($db_duplicate) {
  429                 self::exception(ZBX_API_ERROR_PARAMETERS,
  430                     _s('Discovery rule "%1$s" already exists.', $db_duplicate[0]['name'])
  431                 );
  432             }
  433         }
  434 
  435         // Check proxy IDs.
  436         if ($proxy_hostids) {
  437             $db_proxies = API::proxy()->get([
  438                 'output' => ['proxyid'],
  439                 'proxyids' => $proxy_hostids,
  440                 'preservekeys' => true
  441             ]);
  442             foreach ($proxy_hostids as $proxy_hostid) {
  443                 if (!array_key_exists($proxy_hostid, $db_proxies)) {
  444                     self::exception(ZBX_API_ERROR_PARAMETERS,
  445                         _s('Incorrect value "%1$s" for "%2$s" field.', $proxy_hostid, 'proxy_hostid')
  446                     );
  447                 }
  448             }
  449         }
  450     }
  451 
  452     /**
  453      * Validate discovery checks.
  454      *
  455      * @param array $dchecks
  456      */
  457     protected function validateDChecks(array $dchecks) {
  458         $uniq = 0;
  459         $item_key_parser = new CItemKey();
  460 
  461         if (!is_array($dchecks)) {
  462             self::exception(ZBX_API_ERROR_PARAMETERS,
  463                 _s('Incorrect value for field "%1$s": %2$s.', 'dchecks', _('an array is expected'))
  464             );
  465         }
  466 
  467         foreach ($dchecks as $dcnum => $dcheck) {
  468             if (!is_array($dcheck)) {
  469                 self::exception(ZBX_API_ERROR_PARAMETERS,
  470                     _s('Incorrect value for field "%1$s": %2$s.', 'dchecks', _('an array is expected'))
  471                 );
  472             }
  473 
  474             if (array_key_exists('uniq', $dcheck) && ($dcheck['uniq'] == 1)) {
  475                 if (!in_array($dcheck['type'], [SVC_AGENT, SVC_SNMPv1, SVC_SNMPv2c, SVC_SNMPv3])) {
  476                     self::exception(ZBX_API_ERROR_PARAMETERS,
  477                         _('Only Zabbix agent, SNMPv1, SNMPv2 and SNMPv3 checks can be made unique.')
  478                     );
  479                 }
  480 
  481                 $uniq++;
  482             }
  483 
  484             if (array_key_exists('ports', $dcheck) && !validate_port_list($dcheck['ports'])) {
  485                 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect port range.'));
  486             }
  487 
  488             $dcheck_types = [SVC_SSH, SVC_LDAP, SVC_SMTP, SVC_FTP, SVC_HTTP, SVC_POP, SVC_NNTP, SVC_IMAP, SVC_TCP,
  489                 SVC_AGENT, SVC_SNMPv1, SVC_SNMPv2c, SVC_ICMPPING, SVC_SNMPv3, SVC_HTTPS, SVC_TELNET
  490             ];
  491 
  492             if (!array_key_exists('type', $dcheck)) {
  493                 self::exception(ZBX_API_ERROR_PARAMETERS, _s('Field "%1$s" is mandatory.', 'type'));
  494             }
  495             elseif (!is_numeric($dcheck['type']) || !in_array($dcheck['type'], $dcheck_types)) {
  496                 self::exception(ZBX_API_ERROR_PARAMETERS,
  497                     _s('Incorrect value "%1$s" for "%2$s" field.', $dcheck['type'], 'type')
  498                 );
  499             }
  500             switch ($dcheck['type']) {
  501                 case SVC_AGENT:
  502                     if (!array_key_exists('key_', $dcheck)) {
  503                         self::exception(ZBX_API_ERROR_PARAMETERS, _s('Field "%1$s" is mandatory.', 'key_'));
  504                     }
  505 
  506                     if (is_array($dcheck['key_'])) {
  507                         self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
  508                     }
  509 
  510                     if ($dcheck['key_'] === '' || $dcheck['key_'] === null || $dcheck['key_'] === false) {
  511                         self::exception(ZBX_API_ERROR_PARAMETERS,
  512                             _s('Incorrect value for field "%1$s": %2$s.', 'key_', _('cannot be empty'))
  513                         );
  514                     }
  515 
  516                     $length = mb_strlen($dcheck['key_']);
  517                     if ($length > 255) {
  518                         self::exception(ZBX_API_ERROR_PARAMETERS,
  519                             _s('Incorrect value for field "%1$s": %2$s.', 'key_',
  520                                 _s('%1$d characters exceeds maximum length of %2$d characters', $length, 255)
  521                             )
  522                         );
  523                     }
  524 
  525                     if ($item_key_parser->parse($dcheck['key_']) != CParser::PARSE_SUCCESS) {
  526                         self::exception(ZBX_API_ERROR_PARAMETERS,
  527                             _s('Invalid key "%1$s": %2$s.', $dcheck['key_'], $item_key_parser->getError())
  528                         );
  529                     }
  530                     break;
  531 
  532                 case SVC_SNMPv1:
  533                     // break; is not missing here
  534                 case SVC_SNMPv2c:
  535                     if (!array_key_exists('snmp_community', $dcheck) || $dcheck['snmp_community'] === null
  536                             || $dcheck['snmp_community'] === false || $dcheck['snmp_community'] === '') {
  537                         self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect SNMP community.'));
  538                     }
  539                     // break; is not missing here
  540                 case SVC_SNMPv3:
  541                     if (!array_key_exists('key_', $dcheck) || $dcheck['key_'] === null || $dcheck['key_'] === false
  542                             || $dcheck['key_'] === '') {
  543                         self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect SNMP OID.'));
  544                     }
  545                     break;
  546             }
  547 
  548             // validate snmpv3 fields
  549             if (array_key_exists('snmpv3_securitylevel', $dcheck)
  550                     && $dcheck['snmpv3_securitylevel'] != ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV) {
  551                 // snmpv3 authprotocol
  552                 if ($dcheck['snmpv3_securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV
  553                         || $dcheck['snmpv3_securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV) {
  554                     if (!array_key_exists('snmpv3_authprotocol', $dcheck)
  555                             || $dcheck['snmpv3_authprotocol'] != ITEM_AUTHPROTOCOL_MD5
  556                                 && $dcheck['snmpv3_authprotocol'] != ITEM_AUTHPROTOCOL_SHA) {
  557                         self::exception(ZBX_API_ERROR_PARAMETERS,
  558                             _s('Incorrect value "%1$s" for "%2$s" field.',
  559                                 $dcheck['snmpv3_authprotocol'], 'snmpv3_authprotocol'
  560                             )
  561                         );
  562                     }
  563                 }
  564 
  565                 // snmpv3 privprotocol
  566                 if ($dcheck['snmpv3_securitylevel'] == ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV) {
  567                     if (!array_key_exists('snmpv3_privprotocol', $dcheck)
  568                             || $dcheck['snmpv3_privprotocol'] != ITEM_PRIVPROTOCOL_DES
  569                                 && $dcheck['snmpv3_privprotocol'] != ITEM_PRIVPROTOCOL_AES) {
  570                         self::exception(ZBX_API_ERROR_PARAMETERS,
  571                             _s('Incorrect value "%1$s" for "%2$s" field.',
  572                                 $dcheck['snmpv3_privprotocol'], 'snmpv3_privprotocol'
  573                             )
  574                         );
  575                     }
  576                 }
  577             }
  578 
  579             $this->validateDuplicateChecks($dchecks);
  580         }
  581 
  582         if ($uniq > 1) {
  583             self::exception(ZBX_API_ERROR_PARAMETERS, _('Only one check can be unique.'));
  584         }
  585     }
  586 
  587     protected function validateDuplicateChecks(array $dchecks) {
  588         $default_values = DB::getDefaults('dchecks');
  589 
  590         foreach ($dchecks as &$dcheck) {
  591             // set default values for snmpv3 fields
  592             if (!array_key_exists('snmpv3_securitylevel', $dcheck) || $dcheck['snmpv3_securitylevel'] === null) {
  593                 $dcheck['snmpv3_securitylevel'] = ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV;
  594             }
  595 
  596             switch ($dcheck['snmpv3_securitylevel']) {
  597                 case ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV:
  598                     $dcheck['snmpv3_authprotocol'] = ITEM_AUTHPROTOCOL_MD5;
  599                     $dcheck['snmpv3_privprotocol'] = ITEM_PRIVPROTOCOL_DES;
  600                     $dcheck['snmpv3_authpassphrase'] = '';
  601                     $dcheck['snmpv3_privpassphrase'] = '';
  602                     break;
  603                 case ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV:
  604                     $dcheck['snmpv3_privprotocol'] = ITEM_PRIVPROTOCOL_DES;
  605                     $dcheck['snmpv3_privpassphrase'] = '';
  606                     break;
  607             }
  608 
  609             $dcheck += $default_values;
  610             unset($dcheck['uniq']);
  611         }
  612         unset($dcheck);
  613 
  614         while ($current = array_pop($dchecks)) {
  615             foreach ($dchecks as $dcheck) {
  616                 $equal = true;
  617                 foreach ($dcheck as $field => $value) {
  618                     if (array_key_exists($field, $current) && (strcmp($value, $current[$field]) !== 0)) {
  619                         $equal = false;
  620                         break;
  621                     }
  622                 }
  623                 if ($equal) {
  624                     self::exception(ZBX_API_ERROR_PARAMETERS, _('Checks should be unique.'));
  625                 }
  626             }
  627         }
  628     }
  629 
  630     /**
  631      * Create new discovery rules.
  632      *
  633      * @param array(
  634      *  name => string,
  635      *  proxy_hostid => int,
  636      *  iprange => string,
  637      *  delay => string,
  638      *  status => int,
  639      *  dchecks => array(
  640      *      array(
  641      *          type => int,
  642      *          ports => string,
  643      *          key_ => string,
  644      *          snmp_community => string,
  645      *          snmpv3_securityname => string,
  646      *          snmpv3_securitylevel => int,
  647      *          snmpv3_authpassphrase => string,
  648      *          snmpv3_privpassphrase => string,
  649      *          uniq => int,
  650      *      ), ...
  651      *  )
  652      * ) $drules
  653      *
  654      * @return array
  655      */
  656     public function create(array $drules) {
  657         $drules = zbx_toArray($drules);
  658         $this->validateCreate($drules);
  659 
  660         $druleids = DB::insert('drules', $drules);
  661 
  662         $create_dchecks = [];
  663         foreach ($drules as $dnum => $drule) {
  664             foreach ($drule['dchecks'] as $dcheck) {
  665                 $dcheck['druleid'] = $druleids[$dnum];
  666                 $create_dchecks[] = $dcheck;
  667             }
  668         }
  669 
  670         DB::insert('dchecks', $create_dchecks);
  671 
  672         return ['druleids' => $druleids];
  673     }
  674 
  675     /**
  676      * Update existing drules.
  677      *
  678      * @param array(
  679      *  druleid => int,
  680      *  name => string,
  681      *  proxy_hostid => int,
  682      *  iprange => string,
  683      *  delay => string,
  684      *  status => int,
  685      *  dchecks => array(
  686      *      array(
  687      *          dcheckid => int,
  688      *          type => int,
  689      *          ports => string,
  690      *          key_ => string,
  691      *          snmp_community => string,
  692      *          snmpv3_securityname => string,
  693      *          snmpv3_securitylevel => int,
  694      *          snmpv3_authpassphrase => string,
  695      *          snmpv3_privpassphrase => string,
  696      *          uniq => int,
  697      *      ), ...
  698      *  )
  699      * ) $drules
  700      *
  701      * @return array
  702      */
  703     public function update(array $drules) {
  704         $drules = zbx_toArray($drules);
  705         $druleids = zbx_objectValues($drules, 'druleid');
  706 
  707         $this->validateUpdate($drules);
  708 
  709         $db_drules = API::DRule()->get([
  710             'output' => ['druleid', 'proxy_hostid', 'name', 'iprange', 'delay', 'status'],
  711             'selectDChecks' => ['dcheckid', 'druleid', 'type', 'key_', 'snmp_community', 'ports', 'snmpv3_securityname',
  712                 'snmpv3_securitylevel', 'snmpv3_authpassphrase', 'snmpv3_privpassphrase', 'uniq', 'snmpv3_authprotocol',
  713                 'snmpv3_privprotocol', 'snmpv3_contextname'
  714             ],
  715             'druleids' => $druleids,
  716             'editable' => true,
  717             'preservekeys' => true
  718         ]);
  719 
  720         $default_values = DB::getDefaults('dchecks');
  721 
  722         $upd_drules = [];
  723 
  724         foreach ($drules as $drule) {
  725             $db_drule = $db_drules[$drule['druleid']];
  726 
  727             // Update drule if it's modified.
  728             if (DB::recordModified('drules', $db_drule, $drule)) {
  729                 if (array_key_exists('delay', $drule) && $db_drule['delay'] != $drule['delay']) {
  730                     $drule['nextcheck'] = 0;
  731                 }
  732 
  733                 DB::updateByPk('drules', $drule['druleid'], $drule);
  734             }
  735 
  736             if (array_key_exists('dchecks', $drule)) {
  737                 // Update dchecks.
  738                 $db_dchecks = $db_drule['dchecks'];
  739 
  740                 $new_dchecks = [];
  741                 $old_dchecks = [];
  742 
  743                 foreach ($drule['dchecks'] as $check) {
  744                     $check['druleid'] = $drule['druleid'];
  745 
  746                     if (!isset($check['dcheckid'])) {
  747                         $new_dchecks[] = array_merge($default_values, $check);
  748                     }
  749                     else {
  750                         $old_dchecks[] = $check;
  751                     }
  752                 }
  753 
  754                 $del_dcheckids = array_diff(
  755                     zbx_objectValues($db_dchecks, 'dcheckid'),
  756                     zbx_objectValues($old_dchecks, 'dcheckid')
  757                 );
  758 
  759                 if ($del_dcheckids) {
  760                     $this->deleteActionConditions($del_dcheckids);
  761                 }
  762 
  763                 DB::replace('dchecks', $db_dchecks, array_merge($old_dchecks, $new_dchecks));
  764             }
  765         }
  766 
  767         return ['druleids' => $druleids];
  768     }
  769 
  770     /**
  771      * @param array $druleids
  772      *
  773      * @return array
  774      */
  775     public function delete(array $druleids) {
  776         $api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
  777         if (!CApiInputValidator::validate($api_input_rules, $druleids, '/', $error)) {
  778             self::exception(ZBX_API_ERROR_PARAMETERS, $error);
  779         }
  780 
  781         $db_drules = $this->get([
  782             'output' => ['druleid', 'name'],
  783             'druleids' => $druleids,
  784             'editable' => true,
  785             'preservekeys' => true
  786         ]);
  787 
  788         foreach ($druleids as $druleid) {
  789             if (!array_key_exists($druleid, $db_drules)) {
  790                 self::exception(ZBX_API_ERROR_PERMISSIONS,
  791                     _('No permissions to referred object or it does not exist!')
  792                 );
  793             }
  794         }
  795 
  796         // Check if discovery rules are used in actions.
  797         $db_actions = DBselect(
  798             'SELECT a.name,c.value'.
  799             ' FROM actions a,conditions c'.
  800             ' WHERE a.actionid=c.actionid'.
  801                 ' AND c.conditiontype='.CONDITION_TYPE_DRULE.
  802                 ' AND '.dbConditionString('c.value', $druleids),
  803             1
  804         );
  805 
  806         if ($db_action = DBfetch($db_actions)) {
  807             self::exception(ZBX_API_ERROR_PARAMETERS, _s('Discovery rule "%1$s" is used in "%2$s" action.',
  808                 $db_drules[$db_action['value']]['name'], $db_action['name']
  809             ));
  810         }
  811 
  812         // Check if discovery checks are used in actions.
  813         $db_actions = DBselect(
  814             'SELECT a.name,dc.druleid'.
  815             ' FROM actions a,conditions c,dchecks dc'.
  816             ' WHERE a.actionid=c.actionid'.
  817                 ' AND '.zbx_dbcast_2bigint('c.value').'=dc.dcheckid'.
  818                 ' AND c.conditiontype='.CONDITION_TYPE_DCHECK.
  819                 ' AND '.dbConditionString('dc.druleid', $druleids),
  820             1
  821         );
  822 
  823         if ($db_action = DBfetch($db_actions)) {
  824             self::exception(ZBX_API_ERROR_PARAMETERS, _s('Discovery rule "%1$s" is used in "%2$s" action.',
  825                 $db_drules[$db_action['druleid']]['name'], $db_action['name']
  826             ));
  827         }
  828 
  829         DB::delete('drules', ['druleid' => $druleids]);
  830 
  831         $this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_DISCOVERY_RULE, $db_drules);
  832 
  833         return ['druleids' => $druleids];
  834     }
  835 
  836     /**
  837      * Delete related action conditions.
  838      *
  839      * @param array $dCheckIds
  840      */
  841     protected function deleteActionConditions(array $dCheckIds) {
  842         $actionIds = [];
  843 
  844         // conditions
  845         $dbActions = DBselect(
  846             'SELECT DISTINCT c.actionid'.
  847             ' FROM conditions c'.
  848             ' WHERE c.conditiontype='.CONDITION_TYPE_DCHECK.
  849                 ' AND '.dbConditionString('c.value', $dCheckIds).
  850             ' ORDER BY c.actionid'
  851         );
  852         while ($dbAction = DBfetch($dbActions)) {
  853             $actionIds[] = $dbAction['actionid'];
  854         }
  855 
  856         // disabling actions with deleted conditions
  857         if ($actionIds) {
  858             DB::update('actions', [
  859                 'values' => ['status' => ACTION_STATUS_DISABLED],
  860                 'where' => ['actionid' => $actionIds],
  861             ]);
  862 
  863             DB::delete('conditions', [
  864                 'conditiontype' => CONDITION_TYPE_DCHECK,
  865                 'value' => $dCheckIds
  866             ]);
  867         }
  868     }
  869 
  870     protected function addRelatedObjects(array $options, array $result) {
  871         $result = parent::addRelatedObjects($options, $result);
  872 
  873         $druleids = array_keys($result);
  874 
  875         // Adding Discovery Checks
  876         if (!is_null($options['selectDChecks'])) {
  877             if ($options['selectDChecks'] != API_OUTPUT_COUNT) {
  878                 $relationMap = $this->createRelationMap($result, 'druleid', 'dcheckid', 'dchecks');
  879                 $dchecks = API::DCheck()->get([
  880                     'output' => $options['selectDChecks'],
  881                     'dcheckids' => $relationMap->getRelatedIds(),
  882                     'nopermissions' => true,
  883                     'preservekeys' => true
  884                 ]);
  885                 if (!is_null($options['limitSelects'])) {
  886                     order_result($dchecks, 'dcheckid');
  887                 }
  888                 $result = $relationMap->mapMany($result, $dchecks, 'dchecks', $options['limitSelects']);
  889             }
  890             else {
  891                 $dchecks = API::DCheck()->get([
  892                     'druleids' => $druleids,
  893                     'nopermissions' => true,
  894                     'countOutput' => true,
  895                     'groupCount' => true
  896                 ]);
  897                 $dchecks = zbx_toHash($dchecks, 'druleid');
  898                 foreach ($result as $druleid => $drule) {
  899                     if (isset($dchecks[$druleid]))
  900                         $result[$druleid]['dchecks'] = $dchecks[$druleid]['rowscount'];
  901                     else
  902                         $result[$druleid]['dchecks'] = 0;
  903                 }
  904             }
  905         }
  906 
  907         // Adding Discovery Hosts
  908         if (!is_null($options['selectDHosts'])) {
  909             if ($options['selectDHosts'] != API_OUTPUT_COUNT) {
  910                 $relationMap = $this->createRelationMap($result, 'druleid', 'dhostid', 'dhosts');
  911                 $dhosts = API::DHost()->get([
  912                     'output' => $options['selectDHosts'],
  913                     'dhostids' => $relationMap->getRelatedIds(),
  914                     'preservekeys' => true
  915                 ]);
  916                 if (!is_null($options['limitSelects'])) {
  917                     order_result($dhosts, 'dhostid');
  918                 }
  919                 $result = $relationMap->mapMany($result, $dhosts, 'dhosts', $options['limitSelects']);
  920             }
  921             else {
  922                 $dhosts = API::DHost()->get([
  923                     'druleids' => $druleids,
  924                     'countOutput' => true,
  925                     'groupCount' => true
  926                 ]);
  927                 $dhosts = zbx_toHash($dhosts, 'druleid');
  928                 foreach ($result as $druleid => $drule) {
  929                     if (isset($dhosts[$druleid]))
  930                         $result[$druleid]['dhosts'] = $dhosts[$druleid]['rowscount'];
  931                     else
  932                         $result[$druleid]['dhosts'] = 0;
  933                 }
  934             }
  935         }
  936 
  937         return $result;
  938     }
  939 }