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