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