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 * Controller to build preprocessing test dialog.
24 */
25class CControllerPopupItemTestEdit extends CControllerPopupItemTest {
26
27	protected function checkInput() {
28		$fields = [
29			'authtype'				=> 'in '.implode(',', [HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM, HTTPTEST_AUTH_KERBEROS, HTTPTEST_AUTH_DIGEST, ITEM_AUTHTYPE_PASSWORD, ITEM_AUTHTYPE_PUBLICKEY]),
30			'data'					=> 'array',
31			'delay'					=> 'string',
32			'get_value'				=> 'in 0,1',
33			'headers'				=> 'array',
34			'hostid'				=> 'db hosts.hostid',
35			'http_authtype'			=> 'in '.implode(',', [HTTPTEST_AUTH_NONE, HTTPTEST_AUTH_BASIC, HTTPTEST_AUTH_NTLM, HTTPTEST_AUTH_KERBEROS, HTTPTEST_AUTH_DIGEST, ITEM_AUTHTYPE_PASSWORD, ITEM_AUTHTYPE_PUBLICKEY]),
36			'http_password'			=> 'string',
37			'http_proxy'			=> 'string',
38			'http_username'			=> 'string',
39			'follow_redirects'		=> 'in 0,1',
40			'key'					=> 'string',
41			'interfaceid'			=> 'db interface.interfaceid',
42			'ipmi_sensor'			=> 'string',
43			'itemid'				=> 'db items.itemid',
44			'item_type'				=> 'in '.implode(',', [ITEM_TYPE_ZABBIX, ITEM_TYPE_TRAPPER, ITEM_TYPE_SIMPLE, ITEM_TYPE_INTERNAL, ITEM_TYPE_ZABBIX_ACTIVE, ITEM_TYPE_HTTPTEST, ITEM_TYPE_EXTERNAL, ITEM_TYPE_DB_MONITOR, ITEM_TYPE_IPMI, ITEM_TYPE_SSH, ITEM_TYPE_TELNET, ITEM_TYPE_CALCULATED, ITEM_TYPE_JMX, ITEM_TYPE_SNMPTRAP, ITEM_TYPE_DEPENDENT, ITEM_TYPE_HTTPAGENT, ITEM_TYPE_SNMP, ITEM_TYPE_SCRIPT]),
45			'jmx_endpoint'			=> 'string',
46			'output_format'			=> 'in '.implode(',', [HTTPCHECK_STORE_RAW, HTTPCHECK_STORE_JSON]),
47			'params_ap'				=> 'string',
48			'params_es'				=> 'string',
49			'params_f'				=> 'string',
50			'script'				=> 'string',
51			'password'				=> 'string',
52			'post_type'				=> 'in '.implode(',', [ZBX_POSTTYPE_RAW, ZBX_POSTTYPE_JSON, ZBX_POSTTYPE_XML]),
53			'posts'					=> 'string',
54			'privatekey'			=> 'string',
55			'publickey'				=> 'string',
56			'query_fields'			=> 'array',
57			'parameters'			=> 'array',
58			'request_method'		=> 'in '.implode(',', [HTTPCHECK_REQUEST_GET, HTTPCHECK_REQUEST_POST, HTTPCHECK_REQUEST_PUT, HTTPCHECK_REQUEST_HEAD]),
59			'retrieve_mode'			=> 'in '.implode(',', [HTTPTEST_STEP_RETRIEVE_MODE_CONTENT, HTTPTEST_STEP_RETRIEVE_MODE_HEADERS, HTTPTEST_STEP_RETRIEVE_MODE_BOTH]),
60			'show_final_result'		=> 'in 0,1',
61			'snmp_oid'				=> 'string',
62			'step_obj'				=> 'required|int32',
63			'steps'					=> 'array',
64			'ssl_cert_file'			=> 'string',
65			'ssl_key_file'			=> 'string',
66			'ssl_key_password'		=> 'string',
67			'status_codes'			=> 'string',
68			'test_type'				=> 'required|in '.implode(',', [self::ZBX_TEST_TYPE_ITEM, self::ZBX_TEST_TYPE_ITEM_PROTOTYPE, self::ZBX_TEST_TYPE_LLD]),
69			'timeout'				=> 'string',
70			'username'				=> 'string',
71			'url'					=> 'string',
72			'value_type'			=> 'in '.implode(',', [ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_FLOAT, ITEM_VALUE_TYPE_STR, ITEM_VALUE_TYPE_LOG, ITEM_VALUE_TYPE_TEXT]),
73			'valuemapid'			=> 'int32',
74			'verify_host'			=> 'in 0,1',
75			'verify_peer'			=> 'in 0,1'
76		];
77
78		$ret = $this->validateInput($fields);
79
80		if ($ret) {
81			$testable_item_types = self::getTestableItemTypes($this->getInput('hostid', '0'));
82			$this->item_type = $this->hasInput('item_type') ? $this->getInput('item_type') : -1;
83			$this->preproc_item = self::getPreprocessingItemClassInstance($this->getInput('test_type'));
84			$this->is_item_testable = in_array($this->item_type, $testable_item_types);
85
86			// Check if key is valid for item types it's mandatory.
87			if (in_array($this->item_type, $this->item_types_has_key_mandatory)) {
88				$item_key_parser = new CItemKey();
89
90				if ($item_key_parser->parse($this->getInput('key', '')) != CParser::PARSE_SUCCESS) {
91					error(_s('Incorrect value for field "%1$s": %2$s.', 'key_', $item_key_parser->getError()));
92					$ret = false;
93				}
94			}
95
96			/*
97			 * Either the item must be testable or at least one preprocessing test must be passed ("Test" button should
98			 * be disabled otherwise).
99			 */
100			$steps = $this->getInput('steps', []);
101			if ($ret && $steps) {
102				$steps_validation_response = $this->preproc_item->validateItemPreprocessingSteps($steps);
103				if ($steps_validation_response !== true) {
104					error($steps_validation_response);
105					$ret = false;
106				}
107			}
108			elseif ($ret && !$this->is_item_testable) {
109				error(_s('Test of "%1$s" items is not supported.', item_type2str($this->item_type)));
110				$ret = false;
111			}
112		}
113
114		if (($messages = getMessages(false, null, false)) !== null) {
115			$this->setResponse(
116				(new CControllerResponseData([
117					'main_block' => json_encode(['errors' => $messages->toString()])
118				]))->disableView()
119			);
120		}
121
122		return $ret;
123	}
124
125	protected function doAction() {
126		// VMware and icmpping simple checks are not supported.
127		$key = $this->hasInput('key') ? $this->getInput('key') : '';
128		if ($this->item_type == ITEM_TYPE_SIMPLE
129				&& (substr($key, 0, 7) === 'vmware.' || substr($key, 0, 8) === 'icmpping')) {
130			$this->is_item_testable = false;
131		}
132
133		// Get item and host properties and values from cache.
134		$data = $this->getInput('data', []);
135		$inputs = $this->getItemTestProperties($this->getInputAll());
136
137		// Work with preprocessing steps.
138		$preprocessing_steps_input = $this->getInput('steps', []);
139		$preprocessing_steps = [];
140		foreach ($preprocessing_steps_input as $preproc) {
141			if ($preproc['type'] == ZBX_PREPROC_VALIDATE_NOT_SUPPORTED) {
142				array_unshift($preprocessing_steps, $preproc);
143			}
144			else {
145				$preprocessing_steps[] = $preproc;
146			}
147		}
148
149		$preprocessing_types = zbx_objectValues($preprocessing_steps, 'type');
150		$preprocessing_names = get_preprocessing_types(null, false, $preprocessing_types);
151		$support_lldmacros = ($this->preproc_item instanceof CItemPrototype);
152		$show_prev = (count(array_intersect($preprocessing_types, self::$preproc_steps_using_prev_value)) > 0);
153
154		// Collect item texts and macros to later check their usage.
155		$texts_support_macros = [];
156		$texts_support_user_macros = [];
157		$texts_support_lld_macros = [];
158		$supported_macros = [];
159		foreach (array_keys(array_intersect_key($inputs, $this->macros_by_item_props)) as $field) {
160			// Special processing for calculated item formula.
161			if ($field === 'params_f') {
162				$expression_parser = new CExpressionParser([
163					'usermacros' => true,
164					'lldmacros' => $support_lldmacros,
165					'calculated' => true,
166					'host_macro' => true,
167					'empty_host' => true
168				]);
169
170				if ($expression_parser->parse($inputs[$field]) == CParser::PARSE_SUCCESS) {
171					$tokens = $expression_parser->getResult()->getTokensOfTypes([
172						CExpressionParserResult::TOKEN_TYPE_USER_MACRO,
173						CExpressionParserResult::TOKEN_TYPE_LLD_MACRO,
174						CExpressionParserResult::TOKEN_TYPE_STRING,
175						CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION
176					]);
177					foreach ($tokens as $token) {
178						switch ($token['type']) {
179							case CExpressionParserResult::TOKEN_TYPE_USER_MACRO:
180								$texts_support_user_macros[] = $token['match'];
181								break;
182
183							case CExpressionParserResult::TOKEN_TYPE_LLD_MACRO:
184								$texts_support_lld_macros[] = $token['match'];
185								break;
186
187							case CExpressionParserResult::TOKEN_TYPE_STRING:
188								$text = CExpressionParser::unquoteString($token['match']);
189								$texts_support_user_macros[] = $text;
190								$texts_support_lld_macros[] = $text;
191								break;
192
193							case CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION:
194								foreach ($token['data']['parameters'][0]['data']['filter']['tokens'] as $filter_token) {
195									switch ($filter_token['type']) {
196										case CFilterParser::TOKEN_TYPE_USER_MACRO:
197											$texts_support_user_macros[] = $filter_token['match'];
198											break;
199
200										case CFilterParser::TOKEN_TYPE_LLD_MACRO:
201											$texts_support_lld_macros[] = $filter_token['match'];
202											break;
203
204										case CFilterParser::TOKEN_TYPE_STRING:
205											$text = CFilterParser::unquoteString($filter_token['match']);
206											$texts_support_user_macros[] = $text;
207											$texts_support_lld_macros[] = $text;
208											break;
209									}
210								}
211								break;
212						}
213					}
214				}
215				continue;
216			}
217
218			$macros = $this->macros_by_item_props[$field];
219			unset($macros['support_lld_macros'], $macros['support_user_macros']);
220
221			if ($field === 'query_fields' || $field === 'headers' || $field === 'parameters') {
222				if (!array_key_exists($field, $inputs) || !$inputs[$field]) {
223					continue;
224				}
225
226				foreach (['name', 'value'] as $key) {
227					$texts_having_macros = array_filter($inputs[$field][$key], function($str) {
228						return (strstr($str, '{') !== false);
229					});
230
231					if ($texts_having_macros) {
232						$supported_macros = array_merge_recursive($supported_macros, $macros);
233						$texts_support_macros = array_merge($texts_support_macros, $texts_having_macros);
234						$texts_support_user_macros = array_merge($texts_support_user_macros, $texts_having_macros);
235
236						if ($support_lldmacros) {
237							$texts_support_lld_macros = array_merge($texts_support_lld_macros, $texts_having_macros);
238						}
239					}
240				}
241			}
242			elseif (strstr($inputs[$field], '{') !== false) {
243				// Field support macros like {HOST.*}, {ITEM.*} etc.
244				if ($macros) {
245					$supported_macros = array_merge_recursive($supported_macros, $macros);
246					$texts_support_macros[] = $inputs[$field];
247				}
248
249				// Check if LLD macros are supported in field.
250				if ($support_lldmacros && $this->macros_by_item_props[$field]['support_lld_macros']) {
251					$texts_support_lld_macros[] = $inputs[$field];
252				}
253
254				// Check if user macros are supported in field.
255				if ($this->macros_by_item_props[$field]['support_user_macros']) {
256					$texts_support_user_macros[] = $inputs[$field];
257				}
258			}
259		}
260
261		// Unset duplicate macros.
262		foreach ($supported_macros as &$item_macros_type) {
263			$item_macros_type = array_unique($item_macros_type);
264		}
265		unset($item_macros_type);
266
267		// Extract macros and apply effective values for each of them.
268		$usermacros = CMacrosResolverHelper::extractItemTestMacros([
269			'steps' => $preprocessing_steps,
270			'delay' => $show_prev ? $this->getInput('delay', ZBX_ITEM_DELAY_DEFAULT) : '',
271			'supported_macros' => $supported_macros,
272			'support_lldmacros' => $support_lldmacros,
273			'texts_support_macros' => $texts_support_macros,
274			'texts_support_user_macros' => $texts_support_user_macros,
275			'texts_support_lld_macros' => $texts_support_lld_macros,
276			'hostid' => $this->host ? $this->host['hostid'] : 0,
277			'macros_values' => $this->getSupportedMacros($inputs + ['interfaceid' => $this->getInput('interfaceid', 0)])
278		]);
279
280		$show_warning = false;
281
282		if (array_key_exists('interface', $inputs)) {
283			if (array_key_exists('address', $inputs['interface'])
284					&& strstr($inputs['interface']['address'], ZBX_SECRET_MASK) !== false) {
285				$inputs['interface']['address'] = '';
286				$show_warning = true;
287			}
288
289			if (array_key_exists('port', $inputs['interface']) && $inputs['interface']['port'] === ZBX_SECRET_MASK) {
290				$inputs['interface']['port'] = '';
291				$show_warning = true;
292			}
293
294			if (array_key_exists('details', $inputs['interface'])) {
295				foreach ($inputs['interface']['details'] as $field => $value) {
296					if (strstr($value, ZBX_SECRET_MASK) !== false) {
297						$inputs['interface']['details'][$field] = '';
298						$show_warning = true;
299					}
300				}
301			}
302		}
303
304		// Set resolved macros to previously specified values.
305		foreach (array_keys($usermacros['macros']) as $macro_name) {
306			if ($usermacros['macros'] && array_key_exists('macros', $data) && is_array($data['macros'])
307					&& array_key_exists($macro_name, $data['macros'])) {
308				// Macro values were set by user. Which means those could be intentional asterisks or empty fields.
309				$usermacros['macros'][$macro_name] = $data['macros'][$macro_name];
310			}
311			elseif ($usermacros['macros'][$macro_name] === ZBX_SECRET_MASK) {
312				/*
313				 * Macro values were not set by user, so this means form was opened for the first time. So in this
314				 * case check if there are secret macros. If there are, clear the values and show warning message box.
315				 */
316
317				$usermacros['macros'][$macro_name] = '';
318				$show_warning = true;
319			}
320		}
321
322		// Get previous value and time.
323		$prev_value = '';
324		$prev_time = '';
325		if ($show_prev && array_key_exists('prev_value', $data) && $data['prev_value'] !== '') {
326			$prev_value = $data['prev_value'];
327
328			// Get previous value time.
329			if (array_key_exists('prev_time', $data)) {
330				$prev_time = $data['prev_time'];
331			}
332			else {
333				$delay = timeUnitToSeconds($usermacros['delay']);
334				$prev_time = ($delay !== null && $delay > 0)
335					? 'now-'.$usermacros['delay']
336					: 'now';
337			}
338		}
339
340		// Sort macros.
341		ksort($usermacros['macros']);
342
343		// Add step number and name for each preprocessing step.
344		$num = 0;
345		foreach ($preprocessing_steps as &$step) {
346			$step['name'] = $preprocessing_names[$step['type']];
347			$step['num'] = ++$num;
348		}
349		unset($step);
350
351		$this->setResponse(new CControllerResponseData([
352			'title' => _('Test item'),
353			'steps' => $preprocessing_steps,
354			'value' => array_key_exists('value', $data) ? $data['value'] : '',
355			'eol' => array_key_exists('eol', $data) ? (int) $data['eol'] : ZBX_EOL_LF,
356			'macros' => $usermacros['macros'],
357			'show_prev' => $show_prev,
358			'prev_value' => $prev_value,
359			'prev_time' => $prev_time,
360			'hostid' => $this->getInput('hostid'),
361			'interfaceid' => $this->getInput('interfaceid', 0),
362			'test_type' => $this->getInput('test_type'),
363			'step_obj' => $this->getInput('step_obj'),
364			'show_final_result' => $this->getInput('show_final_result'),
365			'valuemapid' => $this->getInput('valuemapid', 0),
366			'get_value' => array_key_exists('get_value', $data)
367				? $data['get_value']
368				: $this->getInput('get_value', 0),
369			'is_item_testable' => $this->is_item_testable,
370			'inputs' => $inputs,
371			'proxies' => in_array($this->item_type, $this->items_support_proxy) ? $this->getHostProxies() : [],
372			'proxies_enabled' => in_array($this->item_type, $this->items_support_proxy),
373			'interface_address_enabled' => (array_key_exists($this->item_type, $this->items_require_interface)
374				&& $this->items_require_interface[$this->item_type]['address']
375			),
376			'interface_port_enabled' => (array_key_exists($this->item_type, $this->items_require_interface)
377				&& $this->items_require_interface[$this->item_type]['port']
378			),
379			'preproc_item' => $this->preproc_item,
380			'show_snmp_form' => ($this->item_type == ITEM_TYPE_SNMP),
381			'show_warning' => $show_warning,
382			'user' => [
383				'debug_mode' => $this->getDebugMode()
384			]
385		]));
386	}
387}
388