1<?php
2/*
3 +-------------------------------------------------------------------------+
4 | Copyright (C) 2004-2021 The Cacti Group                                 |
5 |                                                                         |
6 | This program is free software; you can redistribute it and/or           |
7 | modify it under the terms of the GNU General Public License             |
8 | as published by the Free Software Foundation; either version 2          |
9 | of the License, or (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 | Cacti: The Complete RRDtool-based Graphing Solution                     |
17 +-------------------------------------------------------------------------+
18 | This code is designed, written, and maintained by the Cacti Group. See  |
19 | about.php and/or the AUTHORS file for specific developer information.   |
20 +-------------------------------------------------------------------------+
21 | http://www.cacti.net/                                                   |
22 +-------------------------------------------------------------------------+
23*/
24
25/**
26 * title_trim - takes a string of text, truncates it to $max_length and appends
27 * three periods onto the end
28 *
29 * @param $text - the string to evaluate
30 * @param $max_length - the maximum number of characters the string can contain
31 *   before it is truncated
32 *
33 * @return - the truncated string if len($text) is greater than $max_length, else
34 *   the original string
35 */
36function title_trim($text, $max_length) {
37	if (strlen($text) > $max_length) {
38		return mb_substr($text, 0, $max_length) . '...';
39	} else {
40		return $text;
41	}
42}
43
44/**
45 * filter_value - a quick way to highlight text in a table from general filtering
46 *
47 * @param $text - the string to filter
48 * @param $filter - the search term to filter for
49 * @param $href - the href if you wish to have an anchor returned
50 *
51 * @return - the filtered string
52 */
53function filter_value($value, $filter, $href = '') {
54	static $charset;
55
56	if ($charset == '') {
57		$charset = ini_get('default_charset');
58	}
59
60	if ($charset == '') {
61		$charset = 'UTF-8';
62	}
63
64	$value =  htmlspecialchars($value, ENT_QUOTES, $charset, false);
65	// Grave Accent character can lead to xss
66	$value = str_replace('`', '&#96;', $value);
67
68	if ($filter != '') {
69		$value = preg_replace('#(' . preg_quote($filter) . ')#i', "<span class='filteredValue'>\\1</span>", $value);
70	}
71
72	if ($href != '') {
73		$value = '<a class="linkEditMain" href="' . htmlspecialchars($href, ENT_QUOTES, $charset, false) . '">' . $value  . '</a>';
74	}
75
76	return $value;
77}
78
79/**
80 * set_graph_config_option - deprecated - wrapper to set_user_setting().
81 *
82 * @param $config_name - the name of the configuration setting as specified $settings array
83 * @param $value       - the values to be saved
84 * @param $user        - the user id, otherwise the session user
85 *
86 * @return            - void
87 */
88function set_graph_config_option($config_name, $value, $user = -1) {
89	set_user_setting($config_name, $value, $user);
90}
91
92/**
93 * graph_config_value_exists - deprecated - wrapper to user_setting_exists
94 *
95 * @param $config_name - the name of the configuration setting as specified $settings_user array
96 *   in 'include/global_settings.php'
97 * @param $user_id - the id of the user to check the configuration value for
98 *
99 * @return (bool) - true if a value exists, false if a value does not exist
100 */
101function graph_config_value_exists($config_name, $user_id) {
102	return user_setting_exists($config_name, $user_id);
103}
104
105/**
106 * read_default_graph_config_option - deprecated - wrapper to read_default_user_setting
107 *
108 * @param $config_name - the name of the configuration setting as specified $settings array
109 *   in 'include/global_settings.php'
110 *
111 * @return - the default value of the configuration option
112 */
113function read_default_graph_config_option($config_name) {
114	return read_default_user_setting($config_name);
115}
116
117/**
118 * read_graph_config_option - deprecated - finds the current value of a graph configuration setting
119 *
120 * @param $config_name - the name of the configuration setting as specified $settings_user array
121 *   in 'include/global_settings.php'
122 *
123 * @return - the current value of the graph configuration option
124 */
125function read_graph_config_option($config_name, $force = false) {
126	return read_user_setting($config_name, false, $force);
127}
128
129/**
130 * save_user_setting - sets/updates aLL user settings
131 *
132 * @param $config_name - the name of the configuration setting as specified $settings array
133 * @param $value       - the values to be saved
134 * @param $user        - the user id, otherwise the session user
135 *
136 * @return            - void
137 */
138function save_user_settings($user = -1) {
139	global $settings_user;
140
141	if ($user == -1 || empty($user)) {
142		$user = $_SESSION['sess_user_id'];
143	}
144
145	foreach ($settings_user as $tab_short_name => $tab_fields) {
146		foreach ($tab_fields as $field_name => $field_array) {
147			/* Check every field with a numeric default value and reset it to default if the inputted value is not numeric  */
148			if (isset($field_array['default']) && is_numeric($field_array['default']) && !is_numeric(get_nfilter_request_var($field_name))) {
149				set_request_var($field_name, $field_array['default']);
150			}
151
152			if (isset($field_array['method'])) {
153				if ($field_array['method'] == 'checkbox') {
154					set_user_setting($field_name, (isset_request_var($field_name) ? 'on' : ''), $user);
155				} elseif ($field_array['method'] == 'checkbox_group') {
156					foreach ($field_array['items'] as $sub_field_name => $sub_field_array) {
157						set_user_setting($sub_field_name, (isset_request_var($sub_field_name) ? 'on' : ''), $user);
158					}
159				} elseif ($field_array['method'] == 'textbox_password') {
160					if (get_nfilter_request_var($field_name) != get_nfilter_request_var($field_name.'_confirm')) {
161						$_SESSION['sess_error_fields'][$field_name] = $field_name;
162						$_SESSION['sess_field_values'][$field_name] = get_nfilter_request_var($field_name);
163						$errors[4] = 4;
164					} elseif (isset_request_var($field_name)) {
165						set_user_setting($field_name, get_nfilter_request_var($field_name), $user);
166					}
167				} elseif ((isset($field_array['items'])) && (is_array($field_array['items']))) {
168					foreach ($field_array['items'] as $sub_field_name => $sub_field_array) {
169						if (isset_request_var($sub_field_name)) {
170							set_user_setting($sub_field_name, get_nfilter_request_var($sub_field_name), $user);
171						}
172					}
173				} elseif (isset_request_var($field_name)) {
174					set_user_setting($field_name, get_nfilter_request_var($field_name), $user);
175				}
176			}
177		}
178	}
179}
180
181/**
182 * set_user_setting - sets/updates a user setting with the given value.
183 *
184 * @param $config_name - the name of the configuration setting as specified $settings array
185 * @param $value       - the values to be saved
186 * @param $user        - the user id, otherwise the session user
187 *
188 * @return          - void
189 */
190function set_user_setting($config_name, $value, $user = -1) {
191	global $settings_user;
192
193	if ($user == -1 && isset($_SESSION['sess_user_id'])) {
194		$user = $_SESSION['sess_user_id'];
195	}
196
197	if ($user == -1) {
198		if (isset($_SESSION['sess_user_id'])) {
199			$mode = 'WEBUI';
200		} else {
201			$mode = 'POLLER';
202		}
203
204		cacti_log('NOTE: Attempt to set user setting \'' . $config_name . '\', with no user id: ' . cacti_debug_backtrace('', false, false, 0, 1), false, $mode, POLLER_VERBOSITY_MEDIUM);
205	} elseif (db_table_exists('settings_user')) {
206		db_execute_prepared('REPLACE INTO settings_user
207			SET user_id = ?,
208			name = ?,
209			value = ?',
210			array($user, $config_name, $value));
211
212		unset($_SESSION['sess_user_config_array']);
213		$settings_user[$config_name]['value'] = $value;
214	}
215}
216
217/**
218 * user_setting_exists - determines if a value exists for the current user/setting specified
219 *
220 * @param $config_name - the name of the configuration setting as specified $settings_user array
221 *   in 'include/global_settings.php'
222 * @param $user_id - the id of the user to check the configuration value for
223 *
224 * @return (bool) - true if a value exists, false if a value does not exist
225 */
226function user_setting_exists($config_name, $user_id) {
227	static $user_setting_values = array();
228
229	if (!isset($user_setting_values[$config_name])) {
230		$value = 0;
231		if (db_table_exists('settings_user')) {
232			$value = db_fetch_cell_prepared('SELECT COUNT(*)
233				FROM settings_user
234				WHERE name = ?
235				AND user_id = ?',
236				array($config_name, $user_id));
237		}
238
239		if ($value !== false && $value > 0) {
240			$user_setting_values[$config_name] = true;
241		} else {
242			$user_setting_values[$config_name] = false;
243		}
244	}
245
246	return $user_setting_values[$config_name];
247}
248
249/**
250 * clear_user_setting - if a value exists for the current user/setting specified, removes it
251 *
252 * @param $config_name - the name of the configuration setting as specified $settings_user array
253 *   in 'include/global_settings.php'
254 * @param $user_id - the id of the user to remove the configuration value for
255 */
256function clear_user_setting($config_name, $user = -1) {
257	global $settings_user;
258
259	if ($user == -1) {
260		$user = $_SESSION['sess_user_id'];
261	}
262
263	if (db_table_exists('settings_user')) {
264		db_execute_prepared('DELETE FROM settings_user
265			WHERE name = ?
266			AND user_id = ?',
267			array($config_name, $user));
268	}
269
270	unset($_SESSION['sess_user_config_array']);
271}
272
273/**
274 * read_default_user_setting - finds the default value of a user configuration setting
275 *
276 * @param $config_name - the name of the configuration setting as specified $settings array
277 *   in 'include/global_settings.php'
278 *
279 * @return - the default value of the configuration option
280 */
281function read_default_user_setting($config_name) {
282	global $config, $settings_user;
283
284	foreach ($settings_user as $tab_array) {
285		if (isset($tab_array[$config_name]) && isset($tab_array[$config_name]['default'])) {
286			return $tab_array[$config_name]['default'];
287		} else {
288			foreach ($tab_array as $field_array) {
289				if (isset($field_array['items']) && isset($field_array['items'][$config_name]) && isset($field_array['items'][$config_name]['default'])) {
290					return $field_array['items'][$config_name]['default'];
291				}
292			}
293		}
294	}
295}
296
297/**
298 * read_user_setting - finds the current value of a graph configuration setting
299 *
300 * @param $config_name - the name of the configuration setting as specified $settings_user array
301 *   in 'include/global_settings.php'
302 * @param $default - the default value is none is set
303 * @param $force - pull the data from the database if true ignoring session
304 * @param $user - assume this user's identity
305 *
306 * @return - the current value of the user setting
307 */
308function read_user_setting($config_name, $default = false, $force = false, $user = 0) {
309	global $config;
310
311	/* users must have cacti user auth turned on to use this, or the guest account must be active */
312	if ($user == 0 && isset($_SESSION['sess_user_id'])) {
313		$effective_uid = $_SESSION['sess_user_id'];
314	} elseif (read_config_option('auth_method') == 0 || $user > 0) {
315		/* first attempt to get the db setting for guest */
316		if ($user == 0) {
317			$effective_uid = db_fetch_cell("SELECT user_auth.id
318				FROM settings
319				INNER JOIN user_auth
320				ON user_auth.username = settings.value
321				WHERE settings.name = 'guest_user'");
322
323			if ($effective_uid == '') {
324				$effective_uid = 0;
325			}
326		} else {
327			$effective_uid = $user;
328		}
329
330		$db_setting = false;
331		if (db_table_exists('settings_user')) {
332			$db_setting = db_fetch_row_prepared('SELECT value
333				FROM settings_user
334				WHERE name = ?
335				AND user_id = ?',
336				array($config_name, $effective_uid));
337		}
338
339		if (cacti_sizeof($db_setting)) {
340			return $db_setting['value'];
341		} elseif ($default !== false) {
342			return $default;
343		} else {
344			return read_default_user_setting($config_name);
345		}
346	} else {
347		$effective_uid = 0;
348	}
349
350	if (!$force) {
351		if (isset($_SESSION['sess_user_config_array'])) {
352			$user_config_array = $_SESSION['sess_user_config_array'];
353		}
354	}
355
356	if (!isset($user_config_array[$config_name])) {
357		$db_setting = false;
358		if (db_table_exists('settings_user')) {
359			$db_setting = db_fetch_row_prepared('SELECT value
360				FROM settings_user
361				WHERE name = ?
362				AND user_id = ?',
363				array($config_name, $effective_uid));
364		}
365
366		if (cacti_sizeof($db_setting)) {
367			$user_config_array[$config_name] = $db_setting['value'];
368		} elseif ($default !== false) {
369			$user_config_array[$config_name] = $default;
370		} else {
371			$user_config_array[$config_name] = read_default_user_setting($config_name);
372		}
373
374		if (isset($_SESSION)) {
375			$_SESSION['sess_user_config_array'] = $user_config_array;
376		} else {
377			$config['config_user_settings_array'] = $user_config_array;
378		}
379	}
380
381	return $user_config_array[$config_name];
382}
383
384/**
385 * set_config_option - sets/updates a cacti config option with the given value.
386 *
387 * @param $config_name - the name of the configuration setting as specified $settings array
388 * @param $value       - the values to be saved
389 *
390 * @return             - void
391 */
392function set_config_option($config_name, $value) {
393	global $config;
394
395	db_execute_prepared('REPLACE INTO settings
396		SET name = ?, value = ?',
397		array($config_name, $value));
398
399	$config_array = array();
400	if (isset($_SESSION['sess_config_array'])) {
401		$config_array = $_SESSION['sess_config_array'];
402	} elseif (isset($config['config_options_array'])) {
403		$config_array = $config['config_options_array'];
404	}
405
406	$config_array[$config_name] = $value;
407
408	// Store the array back for later retrieval
409	if (isset($_SESSION)) {
410		$_SESSION['sess_config_array']  = $config_array;
411	} else {
412		$config['config_options_array'] = $config_array;
413	}
414
415	if (!empty($config['DEBUG_SET_CONFIG_OPTION'])) {
416		file_put_contents(sys_get_temp_dir() . '/cacti-option.log', get_debug_prefix() . cacti_debug_backtrace($config_name, false, false, 0, 1) . "\n", FILE_APPEND);
417	}
418}
419
420/**
421 * config_value_exists - determines if a value exists for the current user/setting specified
422 *
423 * @param $config_name - the name of the configuration setting as specified $settings array
424 *   in 'include/global_settings.php'
425 *
426 * @return - true if a value exists, false if a value does not exist
427 */
428function config_value_exists($config_name) {
429	static $config_values = array();
430
431	if (!isset($config_values[$config_name])) {
432		$value = db_fetch_cell_prepared('SELECT COUNT(*) FROM settings WHERE name = ?', array($config_name));
433
434		if ($value > 0) {
435			$config_values[$config_name] = true;
436		} else {
437			$config_values[$config_name] = false;
438		}
439	}
440
441	return $config_values[$config_name];
442}
443
444/**
445 * read_default_config_option - finds the default value of a Cacti configuration setting
446 *
447 * @param $config_name - the name of the configuration setting as specified $settings array
448 *   in 'include/global_settings.php'
449 *
450 * @return - the default value of the configuration option
451 */
452function read_default_config_option($config_name) {
453	global $config, $settings;
454
455	if (isset($settings) && is_array($settings)) {
456		foreach ($settings as $tab_array) {
457			if (isset($tab_array[$config_name]) && isset($tab_array[$config_name]['default'])) {
458				return $tab_array[$config_name]['default'];
459			} else {
460				foreach ($tab_array as $field_array) {
461					if (isset($field_array['items']) && isset($field_array['items'][$config_name]) && isset($field_array['items'][$config_name]['default'])) {
462						return $field_array['items'][$config_name]['default'];
463					}
464				}
465			}
466		}
467	}
468}
469
470/**
471 * read_config_option - finds the current value of a Cacti configuration setting
472 *
473 * @param $config_name - the name of the configuration setting as specified $settings array
474 *   in 'include/global_settings.php'
475 *
476 * @return - the current value of the configuration option
477 */
478function read_config_option($config_name, $force = false) {
479	global $config, $database_hostname, $database_default, $database_port, $database_sessions;
480
481	$config_array = array();
482	if (isset($_SESSION['sess_config_array'])) {
483		$config_array = $_SESSION['sess_config_array'];
484	} elseif (isset($config['config_options_array'])) {
485		$config_array = $config['config_options_array'];
486	}
487
488	if (!empty($config['DEBUG_READ_CONFIG_OPTION'])) {
489		file_put_contents(sys_get_temp_dir() . '/cacti-option.log', get_debug_prefix() . cacti_debug_backtrace($config_name, false, false, 0, 1) . "\n", FILE_APPEND);
490	}
491
492	// Do we have a value already stored in the array, or
493	// do we want to make sure we have the latest value
494	// from the database?
495	if (!isset($config_array[$config_name]) || ($force)) {
496		// We need to check against the DB, but lets assume default value
497		// unless we can actually read the DB
498		$value = read_default_config_option($config_name);
499
500		if (!empty($config['DEBUG_READ_CONFIG_OPTION'])) {
501			file_put_contents(sys_get_temp_dir() . '/cacti-option.log', get_debug_prefix() .
502				" $config_name: " .
503				' dh: ' . isset($database_hostname) .
504				' dp: ' . isset($database_port) .
505				' dd: ' . isset($database_default) .
506				' ds: ' . isset($database_sessions["$database_hostname:$database_port:$database_default"]) .
507				"\n", FILE_APPEND);
508
509			if (isset($database_hostname) && isset($database_port) && isset($database_default)) {
510				file_put_contents(sys_get_temp_dir() . '/cacti-option.log', get_debug_prefix() .
511					" $config_name: [$database_hostname:$database_port:$database_default]\n", FILE_APPEND);
512			}
513		}
514
515		// Are the database variables set, and do we have a connection??
516		// If we don't, we'll only use the default value without storing
517		// so that we can read the database version later.
518		if (isset($database_hostname) && isset($database_port) && isset($database_default) &&
519		    isset($database_sessions["$database_hostname:$database_port:$database_default"])) {
520
521			// Get the database setting
522			$db_setting = db_fetch_row_prepared('SELECT value FROM settings WHERE name = ?', array($config_name), false);
523
524			// Does the settings exist in the database?
525			if (isset($db_setting['value'])) {
526
527				// It does? lets use it
528				$value = $db_setting['value'];
529			}
530
531			// Store whatever value we have in the array
532			$config_array[$config_name] = $value;
533
534			// Store the array back for later retrieval
535			if (isset($_SESSION)) {
536				$_SESSION['sess_config_array']  = $config_array;
537			} else {
538				$config['config_options_array'] = $config_array;
539			}
540		}
541	} else {
542		// We already have the value stored in the array and
543		// we don't want to force a db read, so use the cached
544		// version
545		$value = $config_array[$config_name];
546	}
547
548	return $value;
549}
550
551/**
552 * get_selected_theme - checks the user settings and if the user selected
553 * theme is set, returns it otherwise returns the system default.
554 *
555 * @return - the theme name
556 */
557function get_selected_theme() {
558	global $config, $themes;
559
560	// shortcut if theme is set in session
561	if (isset($_SESSION['selected_theme'])) {
562		if (file_exists($config['base_path'] . '/include/themes/' . $_SESSION['selected_theme'] . '/main.css')) {
563			return $_SESSION['selected_theme'];
564		}
565	}
566
567	// default to system selected theme
568	$theme = read_config_option('selected_theme');
569
570	// check for a pre-1.x cacti being upgraded
571	if ($theme == '' && !db_table_exists('settings_user')) {
572		return 'modern';
573	}
574
575	// figure out user defined theme
576	if (isset($_SESSION['sess_user_id'])) {
577		// fetch user defined theme
578		$user_theme = db_fetch_cell_prepared("SELECT value
579			FROM settings_user
580			WHERE name='selected_theme'
581			AND user_id = ?",
582			array($_SESSION['sess_user_id']), '', false);
583
584		// user has a theme
585		if (! empty($user_theme)) {
586			$theme = $user_theme;;
587		}
588	}
589
590	if (!file_exists($config['base_path'] . '/include/themes/' . $theme . '/main.css')) {
591		foreach($themes as $t => $name) {
592			if (file_exists($config['base_path'] . '/include/themes/' . $t . '/main.css')) {
593				$theme = $t;
594
595				db_execute_prepared('UPDATE settings_user
596					SET value = ?
597					WHERE user_id = ?
598					AND name="selected_theme"',
599					array($theme, $_SESSION['sess_user_id']));
600
601				break;
602			}
603		}
604	}
605
606	// update session
607	$_SESSION['selected_theme'] = $theme;
608
609	return $theme;
610}
611
612/**
613 * form_input_validate - validates the value of a form field and Takes the appropriate action if the input
614 * is not valid
615 *
616 * @param $field_value - the value of the form field
617 * @param $field_name - the name of the $_POST field as specified in the HTML
618 * @param $regexp_match - (optionally) enter a regular expression to match the value against
619 * @param $allow_nulls - (bool) whether to allow an empty string as a value or not
620 * @param $custom_message - (int) the ID of the message to raise upon an error which is defined in the
621 *   $messages array in 'include/global_arrays.php'
622 *
623 * @return - the original $field_value
624 */
625function form_input_validate($field_value, $field_name, $regexp_match, $allow_nulls, $custom_message = 3) {
626	global $messages;
627
628	/* write current values to the "field_values" array so we can retain them */
629	$_SESSION['sess_field_values'][$field_name] = $field_value;
630
631	if (($allow_nulls == true) && ($field_value == '')) {
632		return $field_value;
633	}
634
635	if ($allow_nulls == false && $field_value == '') {
636		if (read_config_option('log_validation') == 'on') {
637			cacti_log("Form Validation Failed: Variable '$field_name' does not allow nulls and variable is null", false);
638		}
639
640		raise_message($custom_message);
641
642		$_SESSION['sess_error_fields'][$field_name] = $field_name;
643	} elseif ($regexp_match != '' && !preg_match('/' . $regexp_match . '/', $field_value)) {
644		if (read_config_option('log_validation') == 'on') {
645			cacti_log("Form Validation Failed: Variable '$field_name' with Value '$field_value' Failed REGEX '$regexp_match'", false);
646		}
647
648		raise_message($custom_message);
649
650		$_SESSION['sess_error_fields'][$field_name] = $field_name;
651	}
652
653	return $field_value;
654}
655
656/**
657 * check_changed - determines if a request variable has changed between page loads
658 *
659 * @return - true if the value changed between loads
660 */
661function check_changed($request, $session) {
662	if ((isset_request_var($request)) && (isset($_SESSION[$session]))) {
663		if (get_nfilter_request_var($request) != $_SESSION[$session]) {
664			return 1;
665		}
666	}
667}
668
669/**
670 * is_error_message - finds whether an error message has been raised and has not been outputted to the
671 * user
672 *
673 * @return - whether the messages array contains an error or not
674 */
675function is_error_message() {
676	global $config, $messages;
677
678	if (isset($_SESSION['sess_error_fields']) && cacti_sizeof($_SESSION['sess_error_fields'])) {
679		return true;
680	} else {
681		return false;
682	}
683}
684
685function get_message_level($current_message) {
686	$current_level = MESSAGE_LEVEL_NONE;
687
688	if (isset($current_message['level'])) {
689		$current_level = $current_message['level'];
690	} elseif (isset($current_message['type'])) {
691		switch ($current_message['type']) {
692			case 'error':
693				$current_level = MESSAGE_LEVEL_ERROR;
694				break;
695			case 'info':
696				$current_level = MESSAGE_LEVEL_INFO;
697				break;
698		}
699	}
700
701	return $current_level;
702}
703
704/**
705 * get_format_message_instance - finds the level of the current message instance
706 *
707 * @param message array the message instance
708 *
709 * @return - a formatted message
710 */
711function get_format_message_instance($current_message) {
712	if (is_array($current_message)) {
713		$fmessage = isset($current_message['message']) ? $current_message['message'] : __esc('Message Not Found.');
714	} else {
715		$fmessage = $current_message;
716	}
717
718	$level = get_message_level($current_message);
719
720	switch ($level) {
721		case MESSAGE_LEVEL_NONE:
722			$message = '<span>' . $fmessage . '</span>';
723			break;
724		case MESSAGE_LEVEL_INFO:
725			$message = '<span class="deviceUp">' . $fmessage . '</span>';
726			break;
727		case MESSAGE_LEVEL_WARN:
728			$message = '<span class="deviceWarning">' . $fmessage . '</span>';
729			break;
730		case MESSAGE_LEVEL_ERROR:
731			$message = '<span class="deviceDown">' . $fmessage . '</span>';
732			break;
733		case MESSAGE_LEVEL_CSRF:
734			$message = '<span class="deviceDown">' . $fmessage . '</span>';
735			break;
736		default;
737			$message = '<span class="deviceUnknown">' . $fmessage . '</span>';
738			break;
739	}
740
741	return $message;
742}
743
744/**
745 * get_message_max_type - finds the message and returns its type
746 *
747 * @return - the message type 'info', 'warn', 'error' or 'csrf'
748 */
749function get_message_max_type() {
750	global $messages;
751
752	$level = MESSAGE_LEVEL_NONE;
753	if (isset($_SESSION['sess_messages'])) {
754		if (is_array($_SESSION['sess_messages'])) {
755			foreach ($_SESSION['sess_messages'] as $current_message_id => $current_message) {
756				$current_level = get_message_level($current_message);
757				if ($current_level == MESSAGE_LEVEL_NONE && isset($messages[$current_message_id])) {
758					$current_level = get_message_level($messages[$current_message_id]);
759				}
760
761				if ($current_level != $level && $level != MESSAGE_LEVEL_NONE) {
762					$level = MESSAGE_LEVEL_MIXED;
763				} else {
764					$level = $current_level;
765				}
766			}
767		}
768	}
769
770	return $level;
771}
772
773/**
774 * raise_message - mark a message to be displayed to the user once display_output_messages() is called
775 *
776 * @param $message_id - the ID of the message to raise as defined in $messages in 'include/global_arrays.php'
777 */
778function raise_message($message_id, $message = '', $message_level = MESSAGE_LEVEL_NONE) {
779	global $config, $messages, $no_http_headers;
780
781	// This function should always exist, if not its an invalid install
782	if (function_exists('session_status')) {
783		$need_session = (session_status() == PHP_SESSION_NONE) && (!isset($no_http_headers));
784	} else {
785		return false;
786	}
787
788	if (empty($message)) {
789		if (array_key_exists($message_id, $messages)) {
790			$predefined = $messages[$message_id];
791			if (isset($predefined['message'])) {
792				$message = $predefined['message'];
793			} else {
794				$message = $predefined;
795			}
796
797			if ($message_level == MESSAGE_LEVEL_NONE) {
798				$message_level = get_message_level($predefined);
799			}
800		} elseif (isset($_SESSION[$message_id])) {
801			$message = $_SESSION[$message_id];
802			$message_level = MESSAGE_LEVEL_ERROR;
803		} else {
804			$message = __('Message Not Found.');
805			$message_level = MESSAGE_LEVEL_ERROR;
806		}
807	}
808
809	if ($need_session) {
810		cacti_session_start();
811	}
812
813	if (!isset($_SESSION['sess_messages'])) {
814		$_SESSION['sess_messages'] = array();
815	}
816
817	$_SESSION['sess_messages'][$message_id] = array('message' => $message, 'level' => $message_level);
818
819	if ($need_session) {
820		cacti_session_close();
821	}
822}
823
824/**
825 * display_output_messages - displays all of the cached messages from the raise_message() function and clears
826 * the message cache
827 */
828function display_output_messages() {
829	global $messages;
830
831	$omessage      = array();
832	$debug_message = debug_log_return('new_graphs');
833
834	if ($debug_message != '') {
835		$omessage['level']    = MESSAGE_LEVEL_NONE;
836		$omessage['message'] = $debug_message;
837
838		debug_log_clear('new_graphs');
839	} elseif (isset($_SESSION['sess_messages'])) {
840		if (!is_array($_SESSION['sess_messages'])) {
841			$_SESSION['sess_messages'] = array('custom_error' => array('level' => 3, 'message' => $_SESSION['sess_messages']));
842		}
843
844		$omessage['level'] = get_message_max_type();
845
846		foreach ($_SESSION['sess_messages'] as $current_message_id => $current_message) {
847			$message = get_format_message_instance($current_message);
848
849			if (!empty($message)) {
850				$omessage['message'] = (isset($omessage['message']) && $omessage['message'] != '' ? $omessage['message'] . '<br>':'') . $message;
851			} else {
852				cacti_log("ERROR: Cacti Error Message Id '$current_message_id' Not Defined", false, 'WEBUI');
853			}
854		}
855	}
856
857	clear_messages();
858
859	return json_encode($omessage);
860}
861
862function display_custom_error_message($message) {
863	raise_message('custom_error', $message);
864}
865
866/**
867 * clear_messages - clears the message cache
868 */
869function clear_messages() {
870	// This function should always exist, if not its an invalid install
871	if (function_exists('session_status')) {
872		$need_session = (session_status() == PHP_SESSION_NONE) && (!isset($no_http_headers));
873	} else {
874		return false;
875	}
876
877	if ($need_session) {
878		cacti_session_start();
879	}
880
881	kill_session_var('sess_messages');
882
883	if ($need_session) {
884		cacti_session_close();
885	}
886}
887
888/**
889 * kill_session_var - kills a session variable using unset()
890 */
891function kill_session_var($var_name) {
892	/* register_global = on: reset local settings cache so the user sees the new settings */
893	unset($_SESSION[$var_name]);
894
895	/* register_global = off: reset local settings cache so the user sees the new settings */
896	/* session_unregister is deprecated in PHP 5.3.0, unset is sufficient */
897	if (version_compare(PHP_VERSION, '5.3.0', '<')) {
898		session_unregister($var_name);
899	} else {
900		unset($var_name);
901	}
902}
903
904/**
905 * force_session_data - forces session data into the session if the session was closed for some reason
906 */
907function force_session_data() {
908	// This function should always exist, if not its an invalid install
909	if (!function_exists('session_status')) {
910		return false;
911	} elseif (session_status() == PHP_SESSION_NONE) {
912		$data = $_SESSION;
913
914		cacti_session_start();
915
916		$_SESSION = $data;
917
918		cacti_session_close();
919	}
920}
921
922/**
923 * array_rekey - changes an array in the form:
924 *
925 * '$arr[0] = array('id' => 23, 'name' => 'blah')'
926 * to the form
927 * '$arr = array(23 => 'blah')'
928 *
929 * @param $array - (array) the original array to manipulate
930 * @param $key - the name of the key
931 * @param $key_value - the name of the key value
932 *
933 * @return - the modified array
934 */
935function array_rekey($array, $key, $key_value) {
936	$ret_array = array();
937
938	if (is_array($array)) {
939		foreach ($array as $item) {
940			$item_key = $item[$key];
941
942			if (is_array($key_value)) {
943				foreach ($key_value as $value) {
944					$ret_array[$item_key][$value] = $item[$value];
945				}
946			} else {
947				$ret_array[$item_key] = $item[$key_value];
948			}
949		}
950	}
951
952	return $ret_array;
953}
954
955/**
956 * cacti_log_file - returns the log filename
957 */
958function cacti_log_file() {
959	global $config;
960	$logfile        = read_config_option('path_cactilog');
961	if ($logfile == '') {
962		$logfile = '/var/log/cacti/log';
963	}
964	$config['log_path'] = $logfile;
965	return $logfile;
966}
967
968function get_selective_log_level() {
969	static $force_level = null;
970
971	if ($force_level !== null) {
972		return $force_level;
973	}
974
975	if (isset($_SERVER['PHP_SELF'])) {
976		$current_file = basename($_SERVER['PHP_SELF']);
977		$dir_name     = dirname($_SERVER['PHP_SELF']);
978	} elseif (isset($_SERVER['SCRIPT_NAME'])) {
979		$current_file = basename($_SERVER['SCRIPT_NAME']);
980		$dir_name     = dirname($_SERVER['SCRIPT_NAME']);
981	} elseif (isset($_SERVER['SCRIPT_FILENAME'])) {
982		$current_file = basename($_SERVER['SCRIPT_FILENAME']);
983		$dir_name     = dirname($_SERVER['SCRIPT_FILENAME']);
984	} else {
985		$current_file = basename(__FILE__);
986		$dir_name     = dirname(__FILE__);
987	}
988
989	$force_level = '';
990	$debug_files = read_config_option('selective_debug');
991	if ($debug_files != '') {
992		$files = explode(',', $debug_files);
993
994		if (array_search($current_file, $files) !== false) {
995			$force_level = POLLER_VERBOSITY_DEBUG;
996		}
997	}
998
999	if (strpos($dir_name, 'plugins') !== false) {
1000		$debug_plugins = read_config_option('selective_plugin_debug');
1001		if ($debug_plugins != '') {
1002			$debug_plugins = explode(',', $debug_plugins);
1003
1004			foreach($debug_plugins as $myplugin) {
1005				if (strpos($dir_name, DIRECTORY_SEPARATOR . $myplugin) !== false) {
1006					$force_level = POLLER_VERBOSITY_DEBUG;
1007					break;
1008				}
1009			}
1010		}
1011	}
1012
1013	return $force_level;
1014}
1015
1016/**
1017 * cacti_log - logs a string to Cacti's log file or optionally to the browser
1018 *
1019 * @param $string - the string to append to the log file
1020 * @param $output - (bool) whether to output the log line to the browser using print() or not
1021 * @param $environ - (string) tells from where the script was called from
1022 * @param $level - (int) only log if above the specified log level
1023 */
1024function cacti_log($string, $output = false, $environ = 'CMDPHP', $level = '') {
1025	global $config, $database_log;
1026
1027	if (!isset($database_log)) {
1028		$database_log = false;
1029	}
1030
1031	$last_log     = $database_log;
1032	$database_log = false;
1033	$force_level  = get_selective_log_level();
1034
1035	/* only log if the specific level is reached, developer debug is special low + specific devdbg calls */
1036	if ($force_level == '') {
1037		if ($level != '') {
1038			$logVerbosity = read_config_option('log_verbosity');
1039			if ($logVerbosity == POLLER_VERBOSITY_DEVDBG) {
1040				if ($level != POLLER_VERBOSITY_DEVDBG) {
1041					if ($level > POLLER_VERBOSITY_LOW) {
1042						$database_log = $last_log;
1043						return;
1044					}
1045				}
1046			} elseif ($level > $logVerbosity) {
1047				$database_log = $last_log;
1048				return;
1049			}
1050		}
1051	}
1052
1053	/* fill in the current date for printing in the log */
1054	if (defined('CACTI_DATE_TIME_FORMAT')) {
1055		$date = date(CACTI_DATE_TIME_FORMAT);
1056	} else {
1057		$date = date('Y-m-d H:i:s');
1058	}
1059
1060	/* determine how to log data */
1061	$logdestination = read_config_option('log_destination');
1062	$logfile        = cacti_log_file();
1063
1064	/* format the message */
1065	if ($environ == 'POLLER') {
1066		$prefix = "$date - " . $environ . ': Poller[' . $config['poller_id'] . '] PID[' . getmypid() . '] ';
1067	} else {
1068		$prefix = "$date - " . $environ . ' ';
1069	}
1070
1071	/* Log to Logfile */
1072	$message = clean_up_lines($string) . PHP_EOL;
1073	if (($logdestination == 1 || $logdestination == 2) && read_config_option('log_verbosity') != POLLER_VERBOSITY_NONE) {
1074		/* print the data to the log (append) */
1075		$fp = @fopen($logfile, 'a');
1076
1077		if ($fp) {
1078			$message = $prefix . $message;
1079			@fwrite($fp, $message);
1080			fclose($fp);
1081		}
1082	}
1083
1084	/* Log to Syslog/Eventlog */
1085	/* Syslog is currently Unstable in Win32 */
1086	if ($logdestination == 2 || $logdestination == 3) {
1087		$log_type = '';
1088		if (strpos($string, 'ERROR:') !== false) {
1089			$log_type = 'err';
1090		} elseif (strpos($string, 'WARNING:') !== false) {
1091			$log_type = 'warn';
1092		} elseif (strpos($string, 'STATS:') !== false) {
1093			$log_type = 'stat';
1094		} elseif (strpos($string, 'NOTICE:') !== false) {
1095			$log_type = 'note';
1096		}
1097
1098		if ($log_type != '') {
1099			if ($config['cacti_server_os'] == 'win32') {
1100				openlog('Cacti', LOG_NDELAY | LOG_PID, LOG_USER);
1101			} else {
1102				openlog('Cacti', LOG_NDELAY | LOG_PID, LOG_SYSLOG);
1103			}
1104
1105			if ($log_type == 'err' && read_config_option('log_perror')) {
1106				syslog(LOG_CRIT, $environ . ': ' . $string);
1107			} elseif ($log_type == 'warn' && read_config_option('log_pwarn')) {
1108				syslog(LOG_WARNING, $environ . ': ' . $string);
1109			} elseif (($log_type == 'stat' || $log_type == 'note') && read_config_option('log_pstats')) {
1110				syslog(LOG_INFO, $environ . ': ' . $string);
1111			}
1112
1113			closelog();
1114		}
1115	}
1116
1117	/* print output to standard out if required */
1118	if ($output == true && isset($_SERVER['argv'][0])) {
1119		print $message;
1120	}
1121
1122	$database_log = $last_log;
1123}
1124
1125/**
1126 * tail_file - Emulates the tail function with PHP native functions.
1127 * It is used in 0.8.6 to speed the viewing of the Cacti log file, which
1128 * can be problematic in the 0.8.6 branch.
1129 *
1130 * @param $file_name    - (char constant) the name of the file to tail
1131 * @param $line_cnt     - (int constant)  the number of lines to count
1132 * @param $message_type - (int constant) the type of message to return
1133 * @param $filter       - (char) the filtering expression to search for
1134 * @param $page_nr      - (int) the page we want to show rows for
1135 * @param $total_rows   - (int) the total number of rows in the logfile
1136 */
1137function tail_file($file_name, $number_of_lines, $message_type = -1, $filter = '', &$page_nr = 1, &$total_rows = 0) {
1138	if (!file_exists($file_name)) {
1139		touch($file_name);
1140		return array();
1141	}
1142
1143	if (!is_readable($file_name)) {
1144		return array(__('Error %s is not readable', $file_name));
1145	}
1146
1147	$filter = strtolower($filter);
1148
1149	$fp = fopen($file_name, 'r');
1150
1151	/* Count all lines in the logfile */
1152	$total_rows = 0;
1153	while (($line = fgets($fp)) !== false) {
1154		if (determine_display_log_entry($message_type, $line, $filter)) {
1155			++$total_rows;
1156		}
1157	}
1158
1159	// Reset the page count to 1 if the number of lines is exceeded
1160	if (($page_nr - 1) * $number_of_lines > $total_rows) {
1161		set_request_var('page', 1);
1162		$page_nr = 1;
1163	}
1164
1165	/* rewind file pointer, to start all over */
1166	rewind($fp);
1167
1168	$start = $total_rows - ($page_nr * $number_of_lines);
1169	$end   = $start + $number_of_lines;
1170
1171	if ($start < 0) {
1172		$start = 0;
1173	}
1174
1175	force_session_data();
1176
1177	/* load up the lines into an array */
1178	$file_array = array();
1179	$i = 0;
1180	while (($line = fgets($fp)) !== false) {
1181		$display = determine_display_log_entry($message_type, $line, $filter);
1182
1183		if ($display === false) {
1184			continue;
1185		}
1186		if ($i < $start) {
1187			++$i;
1188			continue;
1189		}
1190		if ($i >= $end) {
1191			break;
1192		}
1193
1194		++$i;
1195		$file_array[$i] = $line;
1196	}
1197
1198	fclose($fp);
1199
1200	return $file_array;
1201}
1202
1203/**
1204 * determine_display_log_entry - function to determine if we display the line
1205 *
1206 * @param $message_type
1207 * @param $line
1208 * @param $filter
1209 *
1210 * @return - should the entry be displayed
1211 */
1212function determine_display_log_entry($message_type, $line, $filter) {
1213	/* determine if we are to display the line */
1214	switch ($message_type) {
1215		case 1: /* stats */
1216			$display = (strpos($line, 'STATS') !== false);
1217			break;
1218		case 2: /* warnings */
1219			$display = (strpos($line, 'WARN') !== false);
1220			break;
1221		case 3: /* errors */
1222			$display = (strpos($line, 'ERROR') !== false);
1223			break;
1224		case 4: /* debug */
1225			$display = (strpos($line, 'DEBUG') !== false && strpos($line, ' SQL ') === false);
1226			break;
1227		case 5: /* sql calls */
1228			$display = (strpos($line, ' SQL ') !== false);
1229			break;
1230		default: /* all other lines */
1231		case -1: /* all */
1232			$display = true;
1233			break;
1234	}
1235
1236	/* match any lines that match the search string */
1237	if ($display === true && $filter != '') {
1238		if (stripos($line, $filter) !== false) {
1239			return $line;
1240		} elseif (validate_is_regex($filter) && preg_match('/' . $filter . '/i', $line)) {
1241			return $line;
1242		}
1243		return false;
1244	}
1245
1246	return $display;
1247}
1248
1249/**
1250 * update_host_status - updates the host table with information about its status.
1251 * It will also output to the appropriate log file when an event occurs.
1252 *
1253 * @param $status - (int constant) the status of the host (Up/Down)
1254 * @param $host_id - (int) the host ID for the results
1255 * @param $ping - (class array) results of the ping command.
1256 */
1257function update_host_status($status, $host_id, &$ping, $ping_availability, $print_data_to_stdout) {
1258	$issue_log_message   = false;
1259	$ping_failure_count  = read_config_option('ping_failure_count');
1260	$ping_recovery_count = read_config_option('ping_recovery_count');
1261
1262	$host = db_fetch_row_prepared('SELECT * FROM host WHERE id = ?', array($host_id));
1263
1264	/* initialize fail and recovery dates correctly */
1265	if ($host['status_fail_date'] == '') {
1266		$host['status_fail_date'] = strtotime('0000-00-00 00:00:00');
1267	}
1268
1269	if ($host['status_rec_date'] == '') {
1270		$host['status_rec_date'] = strtotime('0000-00-00 00:00:00');
1271	}
1272
1273	if ($status == HOST_DOWN) {
1274		/* Set initial date down. BUGFIX */
1275		if (empty($host['status_fail_date'])) {
1276			$host['status_fail_date'] = time();
1277		}
1278
1279		/* update total polls, failed polls and availability */
1280		$host['failed_polls']++;
1281		$host['total_polls']++;
1282		$host['availability'] = 100 * ($host['total_polls'] - $host['failed_polls']) / $host['total_polls'];
1283
1284		/* determine the error message to display */
1285		if (($ping_availability == AVAIL_SNMP_AND_PING) || ($ping_availability == AVAIL_SNMP_OR_PING)) {
1286			if (($host['snmp_community'] == '') && ($host['snmp_version'] != 3)) {
1287				/* snmp version 1/2 without community string assume SNMP test to be successful
1288				   due to backward compatibility issues */
1289				$host['status_last_error'] = $ping->ping_response;
1290			} else {
1291				$host['status_last_error'] = $ping->snmp_response . ', ' . $ping->ping_response;
1292			}
1293		} elseif ($ping_availability == AVAIL_SNMP) {
1294			if (($host['snmp_community'] == '') && ($host['snmp_version'] != 3)) {
1295				$host['status_last_error'] = 'Device does not require SNMP';
1296			} else {
1297				$host['status_last_error'] = $ping->snmp_response;
1298			}
1299		} else {
1300			$host['status_last_error'] = $ping->ping_response;
1301		}
1302
1303		/* determine if to send an alert and update remainder of statistics */
1304		if ($host['status'] == HOST_UP) {
1305			/* increment the event failure count */
1306			$host['status_event_count']++;
1307
1308			/* if it's time to issue an error message, indicate so */
1309			if ($host['status_event_count'] >= $ping_failure_count) {
1310				/* host is now down, flag it that way */
1311				$host['status'] = HOST_DOWN;
1312
1313				$issue_log_message = true;
1314
1315				/* update the failure date only if the failure count is 1 */
1316				if ($host['status_event_count'] == $ping_failure_count) {
1317					$host['status_fail_date'] = time();
1318				}
1319			/* host is down, but not ready to issue log message */
1320			} else {
1321				/* host down for the first time, set event date */
1322				if ($host['status_event_count'] == $ping_failure_count) {
1323					$host['status_fail_date'] = time();
1324				}
1325			}
1326		/* host is recovering, put back in failed state */
1327		} elseif ($host['status'] == HOST_RECOVERING) {
1328			$host['status_event_count'] = 1;
1329			$host['status'] = HOST_DOWN;
1330
1331		/* host was unknown and now is down */
1332		} elseif ($host['status'] == HOST_UNKNOWN) {
1333			$host['status'] = HOST_DOWN;
1334			$host['status_event_count'] = 0;
1335		} else {
1336			$host['status_event_count']++;
1337		}
1338	/* host is up!! */
1339	} else {
1340		/* update total polls and availability */
1341		$host['total_polls']++;
1342		$host['availability'] = 100 * ($host['total_polls'] - $host['failed_polls']) / $host['total_polls'];
1343
1344		if ((($ping_availability == AVAIL_SNMP_AND_PING) ||
1345			($ping_availability == AVAIL_SNMP_OR_PING) ||
1346			($ping_availability == AVAIL_SNMP)) &&
1347			(!is_numeric($ping->snmp_status))) {
1348			$ping->snmp_status = 0.000;
1349		}
1350
1351		if ((($ping_availability == AVAIL_SNMP_AND_PING) ||
1352			($ping_availability == AVAIL_SNMP_OR_PING) ||
1353			($ping_availability == AVAIL_PING)) &&
1354			(!is_numeric($ping->ping_status))) {
1355			$ping->ping_status = 0.000;
1356		}
1357
1358		/* determine the ping statistic to set and do so */
1359		if (($ping_availability == AVAIL_SNMP_AND_PING) ||
1360			($ping_availability == AVAIL_SNMP_OR_PING)) {
1361			if (($host['snmp_community'] == '') && ($host['snmp_version'] != 3)) {
1362				$ping_time = 0.000;
1363			} else {
1364				/* calculate the average of the two times */
1365				$ping_time = ($ping->snmp_status + $ping->ping_status) / 2;
1366			}
1367		} elseif ($ping_availability == AVAIL_SNMP) {
1368			if (($host['snmp_community'] == '') && ($host['snmp_version'] != 3)) {
1369				$ping_time = 0.000;
1370			} else {
1371				$ping_time = $ping->snmp_status;
1372			}
1373		} elseif ($ping_availability == AVAIL_NONE) {
1374			$ping_time = 0.000;
1375		} else {
1376			$ping_time = $ping->ping_status;
1377		}
1378
1379		/* update times as required */
1380		if (is_numeric($ping_time)) {
1381			$host['cur_time'] = $ping_time;
1382
1383			/* maximum time */
1384			if ($ping_time > $host['max_time']) {
1385				$host['max_time'] = $ping_time;
1386			}
1387
1388			/* minimum time */
1389			if ($ping_time < $host['min_time']) {
1390				$host['min_time'] = $ping_time;
1391			}
1392
1393			/* average time */
1394			$host['avg_time'] = (($host['total_polls'] - 1 - $host['failed_polls'])
1395				* $host['avg_time'] + $ping_time) / ($host['total_polls'] - $host['failed_polls']);
1396		}
1397
1398		/* the host was down, now it's recovering */
1399		if (($host['status'] == HOST_DOWN) || ($host['status'] == HOST_RECOVERING )) {
1400			/* just up, change to recovering */
1401			if ($host['status'] == HOST_DOWN) {
1402				$host['status'] = HOST_RECOVERING;
1403				$host['status_event_count'] = 1;
1404			} else {
1405				$host['status_event_count']++;
1406			}
1407
1408			/* if it's time to issue a recovery message, indicate so */
1409			if ($host['status_event_count'] >= $ping_recovery_count) {
1410				/* host is up, flag it that way */
1411				$host['status'] = HOST_UP;
1412
1413				$issue_log_message = true;
1414
1415				/* update the recovery date only if the recovery count is 1 */
1416				if ($host['status_event_count'] == $ping_recovery_count) {
1417					$host['status_rec_date'] = time();
1418				}
1419
1420				/* reset the event counter */
1421				$host['status_event_count'] = 0;
1422				/* host is recovering, but not ready to issue log message */
1423			} else {
1424				/* host recovering for the first time, set event date */
1425				if ($host['status_event_count'] == $ping_recovery_count) {
1426					$host['status_rec_date'] = time();
1427				}
1428			}
1429		} else {
1430		/* host was unknown and now is up */
1431			$host['status'] = HOST_UP;
1432			$host['status_event_count'] = 0;
1433		}
1434	}
1435	/* if the user wants a flood of information then flood them */
1436	if (($host['status'] == HOST_UP) || ($host['status'] == HOST_RECOVERING)) {
1437		/* log ping result if we are to use a ping for reachability testing */
1438		if ($ping_availability == AVAIL_SNMP_AND_PING) {
1439			cacti_log("Device[$host_id] PING: " . $ping->ping_response, $print_data_to_stdout, 'PING', POLLER_VERBOSITY_HIGH);
1440			cacti_log("Device[$host_id] SNMP: " . $ping->snmp_response, $print_data_to_stdout, 'PING', POLLER_VERBOSITY_HIGH);
1441		} elseif ($ping_availability == AVAIL_SNMP) {
1442			if (($host['snmp_community'] == '') && ($host['snmp_version'] != 3)) {
1443				cacti_log("Device[$host_id] SNMP: Device does not require SNMP", $print_data_to_stdout, 'PING', POLLER_VERBOSITY_HIGH);
1444			} else {
1445				cacti_log("Device[$host_id] SNMP: " . $ping->snmp_response, $print_data_to_stdout, 'PING', POLLER_VERBOSITY_HIGH);
1446			}
1447		} else {
1448			cacti_log("Device[$host_id] PING: " . $ping->ping_response, $print_data_to_stdout, 'PING', POLLER_VERBOSITY_HIGH);
1449		}
1450	} else {
1451		if ($ping_availability == AVAIL_SNMP_AND_PING) {
1452			cacti_log("Device[$host_id] PING: " . $ping->ping_response, $print_data_to_stdout, 'PING', POLLER_VERBOSITY_HIGH);
1453			cacti_log("Device[$host_id] SNMP: " . $ping->snmp_response, $print_data_to_stdout, 'PING', POLLER_VERBOSITY_HIGH);
1454		} elseif ($ping_availability == AVAIL_SNMP) {
1455			cacti_log("Device[$host_id] SNMP: " . $ping->snmp_response, $print_data_to_stdout, 'PING', POLLER_VERBOSITY_HIGH);
1456		} else {
1457			cacti_log("Device[$host_id] PING: " . $ping->ping_response, $print_data_to_stdout, 'PING', POLLER_VERBOSITY_HIGH);
1458		}
1459	}
1460
1461	/* if there is supposed to be an event generated, do it */
1462	if ($issue_log_message) {
1463		if ($host['status'] == HOST_DOWN) {
1464			cacti_log("Device[$host_id] ERROR: HOST EVENT: Device is DOWN Message: " . $host['status_last_error'], $print_data_to_stdout);
1465		} else {
1466			cacti_log("Device[$host_id] NOTICE: HOST EVENT: Device Returned FROM DOWN State: ", $print_data_to_stdout);
1467		}
1468	}
1469
1470	db_execute_prepared('UPDATE host SET
1471		status = ?,
1472		status_event_count = ?,
1473		status_fail_date = FROM_UNIXTIME(?),
1474		status_rec_date = FROM_UNIXTIME(?),
1475		status_last_error = ?,
1476		min_time = ?,
1477		max_time = ?,
1478		cur_time = ?,
1479		avg_time = ?,
1480		total_polls = ?,
1481		failed_polls = ?,
1482		availability = ?
1483		WHERE hostname = ?
1484		AND deleted = ""',
1485		array(
1486			$host['status'],
1487			$host['status_event_count'],
1488			$host['status_fail_date'],
1489			$host['status_rec_date'],
1490			$host['status_last_error'],
1491			$host['min_time'],
1492			$host['max_time'],
1493			$host['cur_time'],
1494			$host['avg_time'],
1495			$host['total_polls'],
1496			$host['failed_polls'],
1497			$host['availability'],
1498			$host['hostname']
1499		)
1500
1501	);
1502}
1503
1504/**
1505 * is_hexadecimal - test whether a string represents a hexadecimal number,
1506 * ignoring space and tab, and case insensitive.
1507 *
1508 * @param $result - the string to test
1509 * @param 1 if the argument is hex, 0 otherwise, and false on error
1510 */
1511function is_hexadecimal($result) {
1512	$hexstr = str_replace(array(' ', '-'), ':', trim($result));
1513
1514	$parts = explode(':', $hexstr);
1515	foreach($parts as $part) {
1516		if (strlen($part) != 2) {
1517			return false;
1518		}
1519		if (ctype_xdigit($part) == false) {
1520			return false;
1521		}
1522	}
1523
1524	return true;
1525}
1526
1527/**
1528 * strip_domain - removes the domain from a hostname
1529 *
1530 * @param $hostname - the hostname for a device
1531 *
1532 * @return - the stripped hostname
1533 */
1534function strip_domain($hostname) {
1535	if (is_ipaddress($hostname)) {
1536		return $hostname;
1537	} elseif (read_config_option('strip_domain') == 'on') {
1538		$parts = explode('.', $hostname);
1539
1540		return $parts[0];
1541	} else {
1542		return $hostname;
1543	}
1544}
1545
1546
1547/**
1548 * is_mac_address - determines if the result value is a mac address
1549 *
1550 * @param $result - some string to be evaluated
1551 *
1552 * @return - either to result is a mac address of not
1553 */
1554function is_mac_address($result) {
1555	if (!defined('FILTER_VALIDATE_MAC')) {
1556		if (preg_match('/^([0-9a-f]{1,2}[\.:-]) {5}([0-9a-f]{1,2})$/i', $result)) {
1557			return true;
1558		} else {
1559			return false;
1560		}
1561	} else {
1562		return filter_var($result, FILTER_VALIDATE_MAC);
1563	}
1564}
1565
1566function is_hex_string(&$result) {
1567	if ($result == '') {
1568		return false;
1569	}
1570
1571	$compare = strtolower($result);
1572
1573	/* strip off the 'Hex:, Hex-, and Hex-STRING:'
1574	 * Hex- is considered due to the stripping of 'String:' in
1575	 * lib/snmp.php
1576	 */
1577	if (substr($compare, 0, 4) == 'hex-') {
1578		$check = trim(str_ireplace('hex-', '', $result));
1579	} elseif (substr($compare, 0, 11) == 'hex-string:') {
1580		$check = trim(str_ireplace('hex-string:', '', $result));
1581	} else {
1582		return false;
1583	}
1584
1585	$parts = explode(' ', $check);
1586
1587	/* assume if something is a hex string
1588	   it will have a length > 1 */
1589	if (cacti_sizeof($parts) == 1) {
1590		return false;
1591	}
1592
1593	foreach($parts as $part) {
1594		if (strlen($part) != 2) {
1595			return false;
1596		}
1597
1598		if (ctype_xdigit($part) == false) {
1599			return false;
1600		}
1601	}
1602
1603	$result = $check;
1604
1605	return true;
1606}
1607
1608/**
1609 * prepare_validate_result - determines if the result value is valid or not.  If not valid returns a "U"
1610 *
1611 * @param $result - the result from the poll, the result can be modified in the call
1612 *
1613 * @return - either to result is valid or not
1614 */
1615function prepare_validate_result(&$result) {
1616	/* first trim the string */
1617	$result = trim($result, "'\"\n\r");
1618
1619	/* clean off ugly non-numeric data */
1620	if (is_numeric($result)) {
1621		return true;
1622	} elseif ($result == 'U') {
1623		return true;
1624	} elseif (is_hexadecimal($result)) {
1625		return hexdec($result);
1626	} elseif (substr_count($result, ':') || substr_count($result, '!')) {
1627		/* looking for name value pairs */
1628		if (substr_count($result, ' ') == 0) {
1629			return true;
1630		} else {
1631			$delim_cnt = 0;
1632			if (substr_count($result, ':')) {
1633				$delim_cnt = substr_count($result, ':');
1634			} elseif (strstr($result, '!')) {
1635				$delim_cnt = substr_count($result, '!');
1636			}
1637
1638			$space_cnt = substr_count($result, ' ');
1639
1640			return ($space_cnt+1 == $delim_cnt);
1641		}
1642	} else {
1643		$result = strip_alpha($result);
1644
1645		if ($result === false) {
1646			$result = 'U';
1647			return false;
1648		} else {
1649			return true;
1650		}
1651	}
1652}
1653
1654/**
1655 * strip_alpha - remove non-numeric data from a string and return the numeric part
1656 *
1657 * @param $string - the string to be evaluated
1658 *
1659 * @return - either the numeric value or false if not numeric
1660 */
1661function strip_alpha($string) {
1662	/* strip all non numeric data */
1663	$string = trim(preg_replace('/[^0-9,.+-]/', '', $string));
1664
1665	/* check the easy cases first */
1666	/* it has no delimiters, and no space, therefore, must be numeric */
1667	if (is_numeric($string) || is_float($string)) {
1668		return $string;
1669	} else {
1670		return false;
1671	}
1672}
1673
1674/**
1675 * is_valid_pathname - takes a pathname are verifies it matches file name rules
1676 *
1677 * @param $path - the pathname to be tested
1678 *
1679 * @return - either true or false
1680*/
1681function is_valid_pathname($path) {
1682	if (preg_match('/^([a-zA-Z0-9\_\.\-\\\:\/]+)$/', trim($path))) {
1683		return true;
1684	} else {
1685		return false;
1686	}
1687}
1688
1689/**
1690 * dsv_log - provides debug logging when tracing Graph/Data Source creation
1691 *
1692 * @param $message - the message to output to the log
1693 * @param $data  - the data to be carried with the message
1694*/
1695function dsv_log($message,$data) {
1696	if (read_config_option('data_source_trace') == 'on') {
1697		cacti_log(($message . ' = ') . (is_array($data) ? json_encode($data) : $data), false, 'DSV');
1698	}
1699}
1700
1701/**
1702 * test_data_sources
1703 *
1704 * Tests all data sources to confirm that it returns valid data.  This
1705 * function is used by automation to prevent the creation of graphs
1706 * that will never generate data.
1707 *
1708 * @param $graph_template_id - The Graph Template to test
1709 * @param $host_id - The Host to test
1710 *
1711 * @return boolean true or false
1712 */
1713function test_data_sources($graph_template_id, $host_id, $snmp_query_id = 0, $snmp_index = '', $values = array()) {
1714	$data_template_ids = array_rekey(
1715		db_fetch_assoc_prepared('SELECT DISTINCT data_template_id
1716			FROM graph_templates_item AS gti
1717			INNER JOIN data_template_rrd AS dtr
1718			ON gti.task_item_id = dtr.id
1719			WHERE gti.hash != ""
1720			AND gti.local_graph_id = 0
1721			AND dtr.local_data_id = 0
1722			AND gti.graph_template_id = ?',
1723			array($graph_template_id)),
1724		'data_template_id', 'data_template_id'
1725	);
1726
1727	$test_source = db_fetch_cell_prepared('SELECT test_source
1728		FROM graph_templates
1729		WHERE id = ?',
1730		array($graph_template_id));
1731
1732	if (cacti_sizeof($data_template_ids) && $test_source == 'on') {
1733		foreach($data_template_ids as $dt) {
1734			if (!test_data_source($dt, $host_id, $snmp_query_id, $snmp_index, $values)) {
1735				return false;
1736			}
1737		}
1738	}
1739
1740	return true;
1741}
1742
1743/**
1744 * test_data_source
1745 *
1746 * Tests a single data source to confirm that it returns valid data.  This
1747 * function is used by automation to prevent the creation of graphs
1748 * that will never generate data.
1749 *
1750 * @param $graph_template_id - The Graph Template to test
1751 * @param $host_id - The Host to test
1752 *
1753 * @return boolean true or false
1754 */
1755function test_data_source($data_template_id, $host_id, $snmp_query_id = 0, $snmp_index = '', $suggested_vals = array()) {
1756	global $called_by_script_server;
1757
1758	$called_by_script_server = true;
1759
1760	dsv_log('test_data_source', [ 'data_template_id' => $data_template_id, 'host_id' => $host_id, 'snmp_query_id' => $snmp_query_id, 'snmp_index' => $snmp_index, 'suggested_vals' => $suggested_vals]);
1761	$data_input = db_fetch_row_prepared('SELECT ' . SQL_NO_CACHE . '
1762		di.id, di.type_id, dtd.id AS data_template_data_id,
1763		dtd.data_template_id, dtd.active, dtd.rrd_step, di.name
1764		FROM data_template_data AS dtd
1765		INNER JOIN data_input AS di
1766		ON dtd.data_input_id=di.id
1767		WHERE dtd.local_data_id = 0
1768		AND dtd.data_template_id = ?',
1769		array($data_template_id));
1770
1771	dsv_log('data_input', $data_input);
1772
1773	$host = db_fetch_row_prepared('SELECT ' . SQL_NO_CACHE . ' *
1774		FROM host
1775		WHERE id = ?',
1776		array($host_id));
1777
1778	dsv_log('host', $host);
1779
1780	$data_template_data_id = 0;
1781
1782	if (cacti_sizeof($data_input) && $data_input['active'] == 'on') {
1783		$data_template_data_id = $data_input['data_template_data_id'];
1784		/* we have to perform some additional sql queries if this is a 'query' */
1785		if (($data_input['type_id'] == DATA_INPUT_TYPE_SNMP_QUERY) ||
1786			($data_input['type_id'] == DATA_INPUT_TYPE_SCRIPT_QUERY) ||
1787			($data_input['type_id'] == DATA_INPUT_TYPE_QUERY_SCRIPT_SERVER)) {
1788
1789			$field = data_query_field_list($data_template_data_id);
1790			dsv_log('query field', $field);
1791
1792			$params   = array();
1793			$params[] = $data_input['data_template_id'];
1794
1795			if ($field['output_type'] != '') {
1796				$output_type_sql = ' AND sqgr.snmp_query_graph_id = ?';
1797				$params[] = $field['output_type'];
1798			} else {
1799				$output_type_sql = '';
1800			}
1801
1802			$outputs_sql = 'SELECT DISTINCT ' . SQL_NO_CACHE . "
1803				sqgr.snmp_field_name, dtr.id as data_template_rrd_id
1804				FROM snmp_query_graph_rrd AS sqgr
1805				INNER JOIN data_template_rrd AS dtr FORCE INDEX (local_data_id)
1806				ON sqgr.data_template_rrd_id = dtr.id
1807				WHERE sqgr.data_template_id = ?
1808				AND dtr.local_data_id = 0
1809				$output_type_sql
1810				ORDER BY dtr.id";
1811
1812			dsv_log('outputs_sql', $outputs_sql);
1813			dsv_log('outputs_params', $params);
1814
1815			$outputs = db_fetch_assoc_prepared($outputs_sql, $params);
1816
1817			dsv_log('outputs', $outputs);
1818		}
1819
1820		if (($data_input['type_id'] == DATA_INPUT_TYPE_SCRIPT) ||
1821			($data_input['type_id'] == DATA_INPUT_TYPE_PHP_SCRIPT_SERVER)) {
1822			if ($data_input['type_id'] == DATA_INPUT_TYPE_PHP_SCRIPT_SERVER) {
1823				$action = POLLER_ACTION_SCRIPT_PHP;
1824			} else {
1825				$action = POLLER_ACTION_SCRIPT;
1826			}
1827
1828			$script_path = get_full_test_script_path($data_template_id, $host_id);
1829			dsv_log('script_path', $script_path);
1830
1831			$num_output_fields_sql = 'SELECT ' . SQL_NO_CACHE . ' id
1832				FROM data_input_fields
1833				WHERE data_input_id = ?
1834				AND input_output = "out"
1835				AND update_rra="on"';
1836			dsv_log('num_output_fields_sql',$num_output_fields_sql);
1837
1838			$num_output_fields = cacti_sizeof(db_fetch_assoc_prepared($num_output_fields_sql, array($data_input['id'])));
1839			dsv_log('num_output_fields', $num_output_fields);
1840
1841			if ($num_output_fields == 1) {
1842				$data_template_rrd_id = db_fetch_cell_prepared('SELECT ' . SQL_NO_CACHE . ' id
1843					FROM data_template_rrd
1844					WHERE local_data_id = 0
1845					AND hash != ""
1846					AND data_template_id = ?',
1847					array($data_template_id));
1848
1849				$data_source_item_name = get_data_source_item_name($data_template_rrd_id);
1850			} else {
1851				$data_source_item_name = '';
1852			}
1853
1854			dsv_log('data_source_item_name', $data_source_item_name);
1855			if ($action == POLLER_ACTION_SCRIPT) {
1856				dsv_log('script_path', $script_path);
1857				$output = shell_exec($script_path);
1858			} else {
1859				// Script server is a bit more complicated
1860				$php   = read_config_option('path_php_binary');
1861				$parts = explode(' ', $script_path);
1862
1863				dsv_log('parts', $parts);
1864				if (file_exists($parts[0])) {
1865					unset($parts[1]);
1866
1867					$script = implode(' ', $parts);
1868
1869					dsv_log('script', $script);
1870					$output = shell_exec("$php -q $script");
1871					if ($output == '' || $output == false) {
1872						$output = 'U';
1873					}
1874				} else {
1875					$output = 'U';
1876				}
1877			}
1878
1879			dsv_log('output', $output);
1880			if (!is_numeric($output)) {
1881				if ($output == 'U') {
1882					return false;
1883				} elseif (prepare_validate_result($output) === false) {
1884					return false;
1885				}
1886			}
1887
1888			return true;
1889		} elseif ($data_input['type_id'] == DATA_INPUT_TYPE_SNMP) {
1890			/* get host fields first */
1891			$host_fields_sql = 'SELECT ' . SQL_NO_CACHE . ' dif.id, dif.type_code, did.value
1892				FROM data_input_fields AS dif
1893				LEFT JOIN data_input_data AS did
1894				ON dif.id=did.data_input_field_id
1895				WHERE (type_code LIKE "snmp_%" OR type_code IN("hostname","host_id"))
1896				AND did.data_template_data_id = ?
1897				AND did.value != ""';
1898			dsv_log('host_fields_sql',$host_fields_sql);
1899			dsv_log('host_fields_sql_params', ['data_template_data_id' => $data_template_data_id]);
1900
1901			$host_fields = array_rekey(
1902				db_fetch_assoc_prepared($host_fields_sql,
1903					array($data_template_data_id)),
1904				'type_code', 'value'
1905			);
1906
1907			dsv_log('SNMP host_fields', $host_fields);
1908
1909			$data_template_data = db_fetch_assoc_prepared('SELECT ' . SQL_NO_CACHE . ' dif.id, dif.type_code, did.value
1910				FROM data_input_fields AS dif
1911				LEFT JOIN data_input_data AS did
1912				ON dif.id = did.data_input_field_id
1913				WHERE (type_code LIKE "snmp_%" OR type_code="hostname")
1914				AND did.data_template_data_id = ?',
1915				array($data_template_data_id));
1916
1917			dsv_log('SNMP data_template_data', $data_template_data);
1918			if (cacti_sizeof($data_template_data)) {
1919				foreach ($data_template_data as $field) {
1920					$key   = $field['type_code'];
1921					$value = $field['value'];
1922					dsv_log('SNMP field', $field);
1923					//dsv_log('SNMP suggested_val', $suggested_vals['custom_data'][$data_template_id]);
1924					if (!empty($suggested_vals['custom_data'][$data_template_id][$field['id']])) {
1925						$value = $suggested_vals['custom_data'][$data_template_id][$field['id']];
1926						dsv_log("SNMP value replace suggested $key", $value);
1927					}
1928
1929					if (!empty($value) && !isset($host_fields[$key])) {
1930						$host_fields[$key] = $value;
1931						dsv_log("SNMP value replace template $key", $value);
1932					}
1933				}
1934			}
1935
1936			dsv_log('SNMP [updated] host_fields', $host_fields);
1937			$host = array_merge($host, $host_fields);
1938
1939			dsv_log('SNMP [updated] host', $host);
1940
1941			$session = cacti_snmp_session($host['hostname'], $host['snmp_community'], $host['snmp_version'],
1942				$host['snmp_username'], $host['snmp_password'], $host['snmp_auth_protocol'], $host['snmp_priv_passphrase'],
1943				$host['snmp_priv_protocol'], $host['snmp_context'], $host['snmp_engine_id'], $host['snmp_port'],
1944				$host['snmp_timeout'], $host['ping_retries'], $host['max_oids']);
1945
1946			$output = cacti_snmp_session_get($session, $host['snmp_oid']);
1947
1948			dsv_log('SNMP output', $output);
1949
1950			if (!is_numeric($output)) {
1951				if (prepare_validate_result($output) === false) {
1952					return false;
1953				}
1954			}
1955
1956			return true;
1957		} elseif ($data_input['type_id'] == DATA_INPUT_TYPE_SNMP_QUERY) {
1958			$snmp_queries = get_data_query_array($snmp_query_id);
1959
1960			/* get host fields first */
1961			$host_fields = array_rekey(
1962				db_fetch_assoc_prepared('SELECT ' . SQL_NO_CACHE . ' dif.id, dif.type_code, did.value
1963					FROM data_input_fields AS dif
1964					LEFT JOIN data_input_data AS did
1965					ON dif.id=did.data_input_field_id
1966					WHERE (type_code LIKE "snmp_%" OR type_code="hostname")
1967					AND did.data_template_data_id = ?
1968					AND did.value != ""', array($data_template_data_id)),
1969				'type_code', 'value'
1970			);
1971
1972			dsv_log('SNMP_QUERY host_fields', $host_fields);
1973
1974			$data_template_data = db_fetch_assoc_prepared('SELECT ' . SQL_NO_CACHE . ' dif.id, dif.type_code, did.value
1975				FROM data_input_fields AS dif
1976				LEFT JOIN data_input_data AS did
1977				ON dif.id=did.data_input_field_id
1978				WHERE (type_code LIKE "snmp_%" OR type_code="hostname")
1979				AND did.data_template_data_id = ?',
1980				array($data_template_data_id));
1981
1982			dsv_log('SNMP_QUERY data_template_data', $data_template_data);
1983
1984			if (cacti_sizeof($data_template_data)) {
1985				foreach ($data_template_data as $field) {
1986					$key   = $field['type_code'];
1987					$value = $field['value'];
1988					dsv_log('SNMP_QUERY field', $field);
1989					//dsv_log('SNMP_QUERY suggested_val', $suggested_vals['custom_data'][$data_template_id]);
1990					if (!empty($suggested_vals['custom_data'][$data_template_id][$field['id']])) {
1991						$value = $suggested_vals['custom_data'][$data_template_id][$field['id']];
1992						dsv_log("SNMP_QUERY value replace suggested $key", $value);
1993					}
1994
1995					if (!empty($value) && !isset($host_fields[$key])) {
1996						$host_fields[$key] = $value;
1997						dsv_log("SNMP_QUERY value replace template $key", $value);
1998					}
1999				}
2000			}
2001
2002			dsv_log('SNMP_QUERY [updated] host_fields', $host_fields);
2003
2004			$host = array_merge($host, $host_fields);
2005
2006			dsv_log('SNMP_QUERY [updated] host', $host);
2007
2008			if (cacti_sizeof($outputs) && cacti_sizeof($snmp_queries)) {
2009				foreach ($outputs as $output) {
2010					if (isset($snmp_queries['fields'][$output['snmp_field_name']]['oid'])) {
2011						$oid = $snmp_queries['fields'][$output['snmp_field_name']]['oid'] . '.' . $snmp_index;
2012
2013						if (isset($snmp_queries['fields'][$output['snmp_field_name']]['oid_suffix'])) {
2014							$oid .= '.' . $snmp_queries['fields'][$output['snmp_field_name']]['oid_suffix'];
2015						}
2016					}
2017
2018					if (!empty($oid)) {
2019						$session = cacti_snmp_session($host['hostname'], $host['snmp_community'], $host['snmp_version'],
2020							$host['snmp_username'], $host['snmp_password'], $host['snmp_auth_protocol'], $host['snmp_priv_passphrase'],
2021							$host['snmp_priv_protocol'], $host['snmp_context'], $host['snmp_engine_id'], $host['snmp_port'],
2022							$host['snmp_timeout'], $host['ping_retries'], $host['max_oids']);
2023
2024						$output = cacti_snmp_session_get($session, $oid);
2025
2026						if (!is_numeric($output)) {
2027							if (prepare_validate_result($output) === false) {
2028								return false;
2029							}
2030						}
2031
2032						return true;
2033					}
2034				}
2035			}
2036		} elseif (($data_input['type_id'] == DATA_INPUT_TYPE_SCRIPT_QUERY) ||
2037			($data_input['type_id'] == DATA_INPUT_TYPE_QUERY_SCRIPT_SERVER)) {
2038			$script_queries = get_data_query_array($snmp_query_id);
2039
2040			/* get host fields first */
2041			$host_fields = array_rekey(
2042				db_fetch_assoc_prepared('SELECT ' . SQL_NO_CACHE . ' dif.id, dif.type_code, did.value
2043					FROM data_input_fields AS dif
2044					LEFT JOIN data_input_data AS did
2045					ON dif.id=did.data_input_field_id
2046					WHERE (type_code LIKE "snmp_%" OR type_code="hostname")
2047					AND did.data_template_data_id = ?
2048					AND did.value != ""', array($data_template_data_id)),
2049				'type_code', 'value'
2050			);
2051
2052			dsv_log('SCRIPT host_fields', $host_fields);
2053
2054			$data_template_fields = array_rekey(
2055				db_fetch_assoc_prepared('SELECT ' . SQL_NO_CACHE . ' dif.id, dif.type_code, did.value
2056					FROM data_input_fields AS dif
2057					LEFT JOIN data_input_data AS did
2058					ON dif.id=did.data_input_field_id
2059					WHERE (type_code LIKE "snmp_%" OR type_code="hostname")
2060					AND did.data_template_data_id = ?
2061					AND did.value != ""', array($data_template_data_id)),
2062				'type_code', 'value'
2063			);
2064
2065			$data_template_data = db_fetch_assoc_prepared('SELECT ' . SQL_NO_CACHE . ' dif.id, dif.type_code, did.value
2066				FROM data_input_fields AS dif
2067				LEFT JOIN data_input_data AS did
2068				ON dif.id=did.data_input_field_id
2069				WHERE (type_code LIKE "snmp_%" OR type_code="hostname")
2070				AND did.data_template_data_id = ?',
2071				array($data_template_data_id));
2072
2073			dsv_log('SCRIPT data_template_data', $data_template_data);
2074
2075			if (cacti_sizeof($data_template_data)) {
2076				foreach ($data_template_data as $field) {
2077					$key = $field['type_code'];
2078					$value = $field['value'];
2079					dsv_log('SCRIPT field', $field);
2080					//dsv_log('SCRIPT suggested_val', $suggested_vals['custom_data'][$data_template_id]);
2081					if (!empty($suggested_vals['custom_data'][$data_template_id][$field['id']])) {
2082						$value = $suggested_vals['custom_data'][$data_template_id][$field['id']];
2083						dsv_log("SCRIPT value replace suggested $key", $value);
2084					}
2085
2086					if (!empty($value) && !isset($host_fields[$key])) {
2087						$host_fields[$key] = $value;
2088						dsv_log("SCRIPT value replace template $key", $value);
2089					}
2090				}
2091			}
2092
2093			dsv_log('SCRIPT [updated] host_fields', $host_fields);
2094
2095			$host = array_merge($host, $host_fields);
2096
2097			dsv_log('SCRIPT [updated] host', $host);
2098
2099			if (cacti_sizeof($outputs) && cacti_sizeof($script_queries)) {
2100				foreach ($outputs as $output) {
2101					if (isset($script_queries['fields'][$output['snmp_field_name']]['query_name'])) {
2102						$identifier = $script_queries['fields'][$output['snmp_field_name']]['query_name'];
2103
2104						if ($data_input['type_id'] == DATA_INPUT_TYPE_QUERY_SCRIPT_SERVER) {
2105							$action = POLLER_ACTION_SCRIPT;
2106
2107							$prepend = '';
2108							if (isset($script_queries['arg_prepend']) && $script_queries['arg_prepend'] != '') {
2109								$prepend = $script_queries['arg_prepend'];
2110							}
2111
2112							$script_path = read_config_option('path_php_binary') . ' -q ' . get_script_query_path(trim($prepend . ' ' . $script_queries['arg_get'] . ' ' . $identifier . ' ' . $snmp_index), $script_queries['script_path'], $host_id);
2113						} else {
2114							$action = POLLER_ACTION_SCRIPT;
2115							$script_path = get_script_query_path(trim((isset($script_queries['arg_prepend']) ? $script_queries['arg_prepend'] : '') . ' ' . $script_queries['arg_get'] . ' ' . $identifier . ' ' . $snmp_index), $script_queries['script_path'], $host_id);
2116						}
2117					}
2118
2119					if (isset($script_path)) {
2120						$output = shell_exec($script_path);
2121
2122						if (!is_numeric($output)) {
2123							if (prepare_validate_result($output) === false) {
2124								return false;
2125							}
2126						}
2127
2128						return true;
2129					}
2130				}
2131			}
2132		}
2133	}
2134
2135	return false;
2136}
2137
2138/**
2139 * get_full_test_script_path - gets the full path to the script to execute to obtain data for a
2140 * given data template for testing. this function does not work on SNMP actions, only
2141 * script-based actions
2142 *
2143 * @param $data_template_id - (int) the ID of the data template
2144 *
2145 * @return - the full script path or (bool) false for an error
2146 */
2147function get_full_test_script_path($data_template_id, $host_id) {
2148	global $config;
2149
2150	$data_source = db_fetch_row_prepared('SELECT ' . SQL_NO_CACHE . '
2151		dtd.id,
2152		dtd.data_input_id,
2153		di.type_id,
2154		di.input_string
2155		FROM data_template_data AS dtd
2156		INNER JOIN data_input AS di
2157		ON dtd.data_input_id = di.id
2158		WHERE dtd.local_data_id = 0
2159		AND dtd.data_template_id = ?',
2160		array($data_template_id));
2161
2162	$data = db_fetch_assoc_prepared("SELECT " . SQL_NO_CACHE . " dif.data_name, did.value
2163		FROM data_input_fields AS dif
2164		LEFT JOIN data_input_data AS did
2165		ON dif.id = did.data_input_field_id
2166		WHERE dif.data_input_id  = ?
2167		AND did.data_template_data_id = ?
2168		AND dif.input_output = 'in'",
2169		array($data_source['data_input_id'], $data_source['id']));
2170
2171	$full_path = $data_source['input_string'];
2172
2173	$host = db_fetch_row_prepared('SELECT * FROM host WHERE id = ?', array($host_id));
2174
2175	if (cacti_sizeof($data)) {
2176		foreach ($data as $item) {
2177			if (isset($host[$item['data_name']])) {
2178				$value = cacti_escapeshellarg($host[$item['data_name']]);
2179			} elseif ($item['data_name'] == 'host_id' || $item['data_name'] == 'hostid') {
2180				$value = cacti_escapeshellarg($host['id']);
2181			} else {
2182				$value = "'" . $item['value'] . "'";
2183			}
2184
2185			$full_path = str_replace('<' . $item['data_name'] . '>', $value, $full_path);
2186		}
2187	}
2188
2189	$search    = array('<path_cacti>', '<path_snmpget>', '<path_php_binary>');
2190	$replace   = array($config['base_path'], read_config_option('path_snmpget'), read_config_option('path_php_binary'));
2191	$full_path = str_replace($search, $replace, $full_path);
2192
2193	/**
2194	 * sometimes a certain input value will not have anything entered... null out these fields
2195	 * in the input string so we don't mess up the script
2196	 */
2197	return preg_replace('/(<[A-Za-z0-9_]+>)+/', '', $full_path);
2198}
2199
2200/**
2201 * get_full_script_path - gets the full path to the script to execute to obtain data for a
2202 * given data source. this function does not work on SNMP actions, only script-based actions
2203 *
2204 * @param $local_data_id - (int) the ID of the data source
2205 *
2206 * @return - the full script path or (bool) false for an error
2207 */
2208function get_full_script_path($local_data_id) {
2209	global $config;
2210
2211	$data_source = db_fetch_row_prepared('SELECT ' . SQL_NO_CACHE . ' dtd.id, dtd.data_input_id,
2212		di.type_id, di.input_string
2213		FROM data_template_data AS dtd
2214		INNER JOIN data_input AS di
2215		ON dtd.data_input_id = di.id
2216		WHERE dtd.local_data_id = ?',
2217		array($local_data_id));
2218
2219	/* snmp-actions don't have paths */
2220	if (($data_source['type_id'] == DATA_INPUT_TYPE_SNMP) || ($data_source['type_id'] == DATA_INPUT_TYPE_SNMP_QUERY)) {
2221		return false;
2222	}
2223
2224	$data = db_fetch_assoc_prepared("SELECT " . SQL_NO_CACHE . " dif.data_name, did.value
2225		FROM data_input_fields AS dif
2226		LEFT JOIN data_input_data AS did
2227		ON dif.id = did.data_input_field_id
2228		WHERE dif.data_input_id = ?
2229		AND did.data_template_data_id = ?
2230		AND dif.input_output = 'in'",
2231		array($data_source['data_input_id'], $data_source['id']));
2232
2233	$full_path = $data_source['input_string'];
2234
2235	if (cacti_sizeof($data)) {
2236		foreach ($data as $item) {
2237			$value = cacti_escapeshellarg($item['value']);
2238
2239			if ($value == '') {
2240				$value = "''";
2241			}
2242
2243			$full_path = str_replace('<' . $item['data_name'] . '>', $value, $full_path);
2244		}
2245	}
2246
2247	$search    = array('<path_cacti>', '<path_snmpget>', '<path_php_binary>');
2248	$replace   = array($config['base_path'], read_config_option('path_snmpget'), read_config_option('path_php_binary'));
2249	$full_path = str_replace($search, $replace, $full_path);
2250
2251	/* sometimes a certain input value will not have anything entered... null out these fields
2252	in the input string so we don't mess up the script */
2253	return preg_replace('/(<[A-Za-z0-9_]+>)+/', '', $full_path);
2254}
2255
2256/**
2257 * get_data_source_item_name - gets the name of a data source item or generates a new one if one does not
2258 * already exist
2259 *
2260 * @param $data_template_rrd_id - (int) the ID of the data source item
2261 *
2262 * @return - the name of the data source item or an empty string for an error
2263 */
2264function get_data_source_item_name($data_template_rrd_id) {
2265	if (empty($data_template_rrd_id)) {
2266		return '';
2267	}
2268
2269	$data_source = db_fetch_row_prepared('SELECT ' . SQL_NO_CACHE . '
2270		dtr.data_source_name, dtd.name
2271		FROM data_template_rrd AS dtr
2272		INNER JOIN data_template_data AS dtd
2273		ON dtr.local_data_id = dtd.local_data_id
2274		WHERE dtr.id = ?',
2275		array($data_template_rrd_id)
2276	);
2277
2278	/* use the cacti ds name by default or the user defined one, if entered */
2279	if (empty($data_source['data_source_name'])) {
2280		/* limit input to 19 characters */
2281		$data_source_name = clean_up_name($data_source['name']);
2282		$data_source_name = substr(strtolower($data_source_name), 0, (19-strlen($data_template_rrd_id))) . $data_template_rrd_id;
2283
2284		return $data_source_name;
2285	} else {
2286		return $data_source['data_source_name'];
2287	}
2288}
2289
2290/**
2291 * get_data_source_path - gets the full path to the .rrd file associated with a given data source
2292 *
2293 * @param $local_data_id - (int) the ID of the data source
2294 * @param $expand_paths - (bool) whether to expand the <path_rra> variable into its full path or not
2295 *
2296 * @return - the full path to the data source or an empty string for an error
2297 */
2298function get_data_source_path($local_data_id, $expand_paths) {
2299	global $config;
2300	static $data_source_path_cache = array();
2301
2302	if (empty($local_data_id)) {
2303		return '';
2304	}
2305
2306	if (isset($data_source_path_cache[$local_data_id])) {
2307		return $data_source_path_cache[$local_data_id];
2308	}
2309
2310	$data_source = db_fetch_row_prepared('SELECT ' . SQL_NO_CACHE . ' name, data_source_path FROM data_template_data WHERE local_data_id = ?', array($local_data_id));
2311
2312	if (cacti_sizeof($data_source) > 0) {
2313		if (empty($data_source['data_source_path'])) {
2314			/* no custom path was specified */
2315			$data_source_path = generate_data_source_path($local_data_id);
2316		} elseif (!strstr($data_source['data_source_path'], '/')) {
2317			$data_source_path = '<path_rra>/' . $data_source['data_source_path'];
2318		} else {
2319			$data_source_path = $data_source['data_source_path'];
2320		}
2321
2322		/* whether to show the "actual" path or the <path_rra> variable name (for edit boxes) */
2323		if ($expand_paths == true) {
2324			$data_source_path = str_replace('<path_rra>/', $config['rra_path'] . '/', $data_source_path);
2325		}
2326
2327		$data_source_path_cache[$local_data_id] = $data_source_path;
2328
2329		return $data_source_path;
2330	}
2331}
2332
2333/**
2334 * stri_replace - a case insensitive string replace
2335 *
2336 * @param $find - needle
2337 * @param $replace - replace needle with this
2338 * @param $string - haystack
2339 *
2340 * @return - the original string with '$find' replaced by '$replace'
2341 */
2342function stri_replace($find, $replace, $string) {
2343	$parts = explode(strtolower($find), strtolower($string));
2344
2345	$pos = 0;
2346
2347	$findLength = strlen($find);
2348	foreach ($parts as $key => $part) {
2349		$partLength = strlen($part);
2350
2351		$parts[$key] = substr($string, $pos, $partLength);
2352		$pos += $partLength + $findLength;
2353	}
2354
2355	return (join($replace, $parts));
2356}
2357
2358/**
2359 * clean_up_lines - runs a string through a regular expression designed to remove
2360 * new lines and the spaces around them
2361 *
2362 * @param $string - the string to modify/clean
2363 *
2364 * @return - the modified string
2365 */
2366function clean_up_lines($string) {
2367	return preg_replace('/\s*[\r\n]+\s*/',' ', $string);
2368}
2369
2370/**
2371 * clean_up_name - runs a string through a series of regular expressions designed to
2372 * eliminate "bad" characters
2373 *
2374 * @param $string - the string to modify/clean
2375 *
2376 * @return - the modified string
2377 */
2378function clean_up_name($string) {
2379	$string = preg_replace('/[\s\.]+/', '_', $string);
2380	$string = preg_replace('/[^a-zA-Z0-9_]+/', '', $string);
2381	$string = preg_replace('/_{2,}/', '_', $string);
2382
2383	return $string;
2384}
2385
2386/**
2387 * clean_up_file name - runs a string through a series of regular expressions designed to
2388 * eliminate "bad" characters
2389 *
2390 * @param $string - the string to modify/clean
2391 *
2392 * @return - the modified string
2393 */
2394function clean_up_file_name($string) {
2395	$string = preg_replace('/[\s\.]+/', '_', $string);
2396	$string = preg_replace('/[^a-zA-Z0-9_-]+/', '', $string);
2397	$string = preg_replace('/_{2,}/', '_', $string);
2398
2399	return $string;
2400}
2401
2402/**
2403 * clean_up_path - takes any path and makes sure it contains the correct directory
2404 * separators based on the current operating system
2405 *
2406 * @param $path - the path to modify
2407 *
2408 * @return - the modified path
2409 */
2410function clean_up_path($path) {
2411	global $config;
2412
2413	if ($config['cacti_server_os'] == 'win32') {
2414		return str_replace('/', "\\", $path);
2415	} elseif ($config['cacti_server_os'] == 'unix' || read_config_option('using_cygwin') == 'on' || read_config_option('storage_location')) {
2416		return str_replace("\\", '/', $path);
2417	} else {
2418		return $path;
2419	}
2420}
2421
2422/**
2423 * get_data_source_title - returns the title of a data source without using the title cache
2424 *
2425 * @param $local_data_id - (int) the ID of the data source to get a title for
2426 *
2427 * @return - the data source title
2428 */
2429function get_data_source_title($local_data_id) {
2430	$data = db_fetch_row_prepared('SELECT dl.host_id, dl.snmp_query_id, dl.snmp_index, dtd.name
2431		FROM data_local AS dl
2432		INNER JOIN data_template_data AS dtd
2433		ON dtd.local_data_id = dl.id
2434		WHERE dl.id = ?',
2435		array($local_data_id));
2436
2437	if (cacti_sizeof($data)) {
2438		if (strstr($data['name'], '|') !== false && $data['host_id'] > 0) {
2439			$data['name'] = substitute_data_input_data($data['name'], '', $local_data_id);
2440			return expand_title($data['host_id'], $data['snmp_query_id'], $data['snmp_index'], $data['name']);
2441		} else {
2442			return $data['name'];
2443		}
2444	} else {
2445		return 'Missing Datasource ' . $local_data_id;
2446	}
2447}
2448
2449/**
2450 * get_device_name - returns the description of the device in cacti host table
2451 *
2452 * @param $host_id - (int) the ID of the device to get a description for
2453 *
2454 * @return - the device name
2455 */
2456function get_device_name($host_id) {
2457	return db_fetch_cell_prepared('SELECT description FROM host WHERE id = ?', array($host_id));
2458}
2459
2460/**
2461 * get_color - returns the hex color value from the cacti colors table
2462 *
2463 * @param $color_id - (int) the ID of the cacti color
2464 * @return - the hex color value
2465 *
2466 */
2467function get_color($color_id) {
2468	return db_fetch_cell_prepared('SELECT hex FROM colors WHERE id = ?', array($color_id));
2469}
2470
2471/**
2472 * get_graph_title_cache - returns the title of the graph using the title cache
2473 *
2474 * @param $local_graph_id - (int) the ID of the graph to get the title for
2475 *
2476 * @return - the graph title
2477 */
2478function get_graph_title_cache($local_graph_id) {
2479	return db_fetch_cell_prepared('SELECT title_cache
2480		FROM graph_templates_graph
2481		WHERE local_graph_id = ?',
2482		array($local_graph_id));
2483}
2484
2485/**
2486 * get_graph_title - returns the title of a graph without using the title cache
2487 *
2488 * @param $local_graph_id - (int) the ID of the graph to get a title for
2489 *
2490 * @return - the graph title
2491 */
2492function get_graph_title($local_graph_id) {
2493	$graph = db_fetch_row_prepared('SELECT gl.host_id, gl.snmp_query_id,
2494		gl.snmp_index, gtg.local_graph_id, gtg.t_title, gtg.title
2495		FROM graph_templates_graph AS gtg
2496		INNER JOIN graph_local AS gl
2497		ON gtg.local_graph_id = gl.id
2498		WHERE gl.id = ?',
2499		array($local_graph_id));
2500
2501	if (cacti_sizeof($graph)) {
2502		if (strstr($graph['title'], '|') !== false && $graph['host_id'] > 0 && empty($graph['t_title'])) {
2503			$graph['title'] = substitute_data_input_data($graph['title'], $graph, 0);
2504			return expand_title($graph['host_id'], $graph['snmp_query_id'], $graph['snmp_index'], $graph['title']);
2505		} else {
2506			return $graph['title'];
2507		}
2508	} else {
2509		return '';
2510	}
2511}
2512
2513/**
2514 * get_username - returns the username for the selected user
2515 *
2516 * @param $user_id - (int) the ID of the user
2517 *
2518 * @return - the username */
2519function get_username($user_id) {
2520	return db_fetch_cell_prepared('SELECT username FROM user_auth WHERE id = ?', array($user_id));
2521}
2522
2523/**
2524 * get_execution_user - returns the username of the running process
2525 *
2526 * @return - the username
2527 */
2528function get_execution_user() {
2529	if (function_exists('posix_getpwuid')) {
2530		$user_info = posix_getpwuid(posix_geteuid());
2531
2532		return $user_info['name'];
2533	} else {
2534		return exec('whoami');
2535	}
2536}
2537
2538/**
2539 * generate_data_source_path - creates a new data source path from scratch using the first data source
2540 * item name and updates the database with the new value
2541 *
2542 * @param $local_data_id - (int) the ID of the data source to generate a new path for
2543 *
2544 * @return - the new generated path
2545 */
2546function generate_data_source_path($local_data_id) {
2547	global $config;
2548
2549	/* try any prepend the name with the host description */
2550	$host = db_fetch_row_prepared('SELECT host.id, host.description
2551		FROM (host, data_local)
2552		WHERE data_local.host_id = host.id
2553		AND data_local.id = ?
2554		LIMIT 1', array($local_data_id));
2555
2556	$host_name = $host['description'];
2557	$host_id   = $host['id'];
2558
2559	/* put it all together using the local_data_id at the end */
2560	if (read_config_option('extended_paths') == 'on') {
2561		$new_path = "<path_rra>/$host_id/$local_data_id.rrd";
2562	} else {
2563		if (!empty($host_name)) {
2564			$host_part = strtolower(clean_up_file_name($host_name)) . '_';
2565		}
2566
2567		/* then try and use the internal DS name to identify it */
2568		$data_source_rrd_name = db_fetch_cell_prepared('SELECT data_source_name
2569			FROM data_template_rrd
2570			WHERE local_data_id = ?
2571			ORDER BY id
2572			LIMIT 1',
2573			array($local_data_id)
2574		);
2575
2576		if (!empty($data_source_rrd_name)) {
2577			$ds_part = strtolower(clean_up_file_name($data_source_rrd_name));
2578		} else {
2579			$ds_part = 'ds';
2580		}
2581
2582		$new_path = "<path_rra>/$host_part$ds_part" . '_' . "$local_data_id.rrd";
2583	}
2584
2585	/* update our changes to the db */
2586	db_execute_prepared('UPDATE data_template_data SET data_source_path = ? WHERE local_data_id = ?', array($new_path, $local_data_id));
2587
2588	return $new_path;
2589}
2590
2591/**
2592 * generate graph_best_cf - takes the requested consolidation function and maps against
2593 * the list of available consolidation functions for the consolidation functions and returns
2594 * the most appropriate.  Typically, this will be the requested value
2595 *
2596 *  @param $data_template_id
2597 *  @param $requested_cf
2598 *  @param $ds_step
2599 *
2600 *  @return - the best cf to use
2601 */
2602function generate_graph_best_cf($local_data_id, $requested_cf, $ds_step = 60) {
2603	static $best_cf;
2604
2605	if ($local_data_id > 0) {
2606		$avail_cf_functions = get_rrd_cfs($local_data_id);
2607
2608		if (cacti_sizeof($avail_cf_functions)) {
2609			/* workaround until we have RRA presets in 0.8.8 */
2610			/* check through the cf's and get the best */
2611			/* if none was found, take the first */
2612			$best_cf = $avail_cf_functions[1];
2613
2614			foreach($avail_cf_functions as $cf) {
2615				if ($cf == $requested_cf) {
2616					$best_cf = $requested_cf;
2617				}
2618			}
2619		} else {
2620			$best_cf = '1';
2621		}
2622	}
2623
2624	/* if you can not figure it out return average */
2625	return $best_cf;
2626}
2627
2628/**
2629 * get_rrd_cfs - reads the RRDfile and gets the RRAs stored in it.
2630 *
2631 * @param $local_data_id
2632 *
2633 * @return - array of the CF functions
2634 */
2635function get_rrd_cfs($local_data_id) {
2636	global $consolidation_functions;
2637	static $rrd_cfs = array();
2638
2639	if (array_key_exists($local_data_id, $rrd_cfs)) {
2640		return $rrd_cfs[$local_data_id];
2641	}
2642
2643	$cfs = array();
2644
2645	$rrdfile = get_data_source_path($local_data_id, true);
2646
2647	$output = @rrdtool_execute("info $rrdfile", false, RRDTOOL_OUTPUT_STDOUT);
2648
2649	/* search for
2650	 * 		rra[0].cf = 'LAST'
2651	 * or similar
2652	 */
2653	if ($output != '') {
2654		$output = explode("\n", $output);
2655
2656		if (cacti_sizeof($output)) {
2657			foreach($output as $line) {
2658				if (substr_count($line, '.cf')) {
2659					$values = explode('=',$line);
2660
2661					if (!in_array(trim($values[1], '" '), $cfs)) {
2662						$cfs[] = trim($values[1], '" ');
2663					}
2664				}
2665			}
2666		}
2667	}
2668
2669	$new_cfs = array();
2670
2671	if (cacti_sizeof($cfs)) {
2672		foreach($cfs as $cf) {
2673			switch($cf) {
2674			case 'AVG':
2675			case 'AVERAGE':
2676				$new_cfs[1] = array_search('AVERAGE', $consolidation_functions);
2677				break;
2678			case 'MIN':
2679				$new_cfs[2] = array_search('MIN', $consolidation_functions);
2680				break;
2681			case 'MAX':
2682				$new_cfs[3] = array_search('MAX', $consolidation_functions);
2683				break;
2684			case 'LAST':
2685				$new_cfs[4] = array_search('LAST', $consolidation_functions);
2686				break;
2687			}
2688		}
2689	}
2690
2691	$rrd_cfs[$local_data_id] = $new_cfs;
2692
2693	return $new_cfs;
2694}
2695
2696/**
2697 * generate_graph_def_name - takes a number and turns each digit into its letter-based
2698 * counterpart for RRDtool DEF names (ex 1 -> a, 2 -> b, etc)
2699 *
2700 * @param $graph_item_id - (int) the ID to generate a letter-based representation of
2701 *
2702 * @return - a letter-based representation of the input argument
2703 */
2704function generate_graph_def_name($graph_item_id) {
2705	$lookup_table = array('a','b','c','d','e','f','g','h','i','j');
2706
2707	$result = '';
2708	$strValGII = strval($graph_item_id);
2709	for ($i=0; $i<strlen($strValGII); $i++) {
2710		$result .= $lookup_table[substr($strValGII, $i, 1)];
2711	}
2712
2713	if (preg_match('/^(cf|cdef|def)$/', $result)) {
2714		return 'zz' . $result;
2715	} else {
2716		return $result;
2717	}
2718}
2719
2720/**
2721 * generate_data_input_field_sequences - re-numbers the sequences of each field associated
2722 * with a particular data input method based on its position within the input string
2723 *
2724 * @param $string - the input string that contains the field variables in a certain order
2725 * @param $data_input_id - (int) the ID of the data input method
2726 */
2727function generate_data_input_field_sequences($string, $data_input_id) {
2728	global $config, $registered_cacti_names;
2729
2730	if (preg_match_all('/<([_a-zA-Z0-9]+)>/', $string, $matches)) {
2731		$j = 0;
2732		for ($i=0; ($i < cacti_count($matches[1])); $i++) {
2733			if (in_array($matches[1][$i], $registered_cacti_names) == false) {
2734				$j++;
2735
2736				db_execute_prepared("UPDATE data_input_fields
2737					SET sequence = ?
2738					WHERE data_input_id = ?
2739					AND input_output IN ('in')
2740					AND data_name = ?",
2741					array($j, $data_input_id, $matches[1][$i]));
2742			}
2743		}
2744
2745		update_replication_crc(0, 'poller_replicate_data_input_fields_crc');
2746	}
2747}
2748
2749/**
2750 * move_graph_group - takes a graph group (parent+children) and swaps it with another graph
2751 * group
2752 *
2753 * @param $graph_template_item_id - (int) the ID of the (parent) graph item that was clicked
2754 * @param $graph_group_array - (array) an array containing the graph group to be moved
2755 * @param $target_id - (int) the ID of the (parent) graph item of the target group
2756 * @param $direction - ('next' or 'previous') whether the graph group is to be swapped with
2757 *   group above or below the current group
2758 */
2759function move_graph_group($graph_template_item_id, $graph_group_array, $target_id, $direction) {
2760	$graph_item = db_fetch_row_prepared('SELECT local_graph_id, graph_template_id
2761		FROM graph_templates_item
2762		WHERE id = ?',
2763		array($graph_template_item_id));
2764
2765	if (empty($graph_item['local_graph_id'])) {
2766		$sql_where = 'graph_template_id = ' . $graph_item['graph_template_id'] . ' AND local_graph_id = 0';
2767	} else {
2768		$sql_where = 'local_graph_id = ' . $graph_item['local_graph_id'];
2769	}
2770
2771	/* get a list of parent+children of our target group */
2772	$target_graph_group_array = get_graph_group($target_id);
2773
2774	/* if this "parent" item has no children, then treat it like a regular gprint */
2775	if (cacti_sizeof($target_graph_group_array) == 0) {
2776		if ($direction == 'next') {
2777			move_item_down('graph_templates_item', $graph_template_item_id, $sql_where);
2778		} elseif ($direction == 'previous') {
2779			move_item_up('graph_templates_item', $graph_template_item_id, $sql_where);
2780		}
2781
2782		return;
2783	}
2784
2785	/* start the sequence at '1' */
2786	$sequence_counter = 1;
2787
2788	$graph_items = db_fetch_assoc_prepared("SELECT id, sequence
2789		FROM graph_templates_item
2790		WHERE $sql_where
2791		ORDER BY sequence");
2792
2793	if (cacti_sizeof($graph_items)) {
2794		foreach ($graph_items as $item) {
2795			/* check to see if we are at the "target" spot in the loop; if we are, update the sequences and move on */
2796			if ($target_id == $item['id']) {
2797				if ($direction == 'next') {
2798					$group_array1 = $target_graph_group_array;
2799					$group_array2 = $graph_group_array;
2800				} elseif ($direction == 'previous') {
2801					$group_array1 = $graph_group_array;
2802					$group_array2 = $target_graph_group_array;
2803				}
2804
2805				foreach ($group_array1 as $graph_template_item_id) {
2806					db_execute_prepared('UPDATE graph_templates_item
2807						SET sequence = ?
2808						WHERE id = ?',
2809						array($sequence_counter, $graph_template_item_id));
2810
2811					/* propagate to ALL graphs using this template */
2812					if (empty($graph_item['local_graph_id'])) {
2813						db_execute_prepared('UPDATE graph_templates_item
2814							SET sequence = ?
2815							WHERE local_graph_template_item_id = ?',
2816							array($sequence_counter, $graph_template_item_id));
2817					}
2818
2819					$sequence_counter++;
2820				}
2821
2822				foreach ($group_array2 as $graph_template_item_id) {
2823					db_execute_prepared('UPDATE graph_templates_item
2824						SET sequence = ?
2825						WHERE id = ?',
2826						array($sequence_counter, $graph_template_item_id));
2827
2828					/* propagate to ALL graphs using this template */
2829					if (empty($graph_item['local_graph_id'])) {
2830						db_execute_prepared('UPDATE graph_templates_item
2831							SET sequence = ?
2832							WHERE local_graph_template_item_id = ?',
2833							array($sequence_counter, $graph_template_item_id));
2834					}
2835
2836					$sequence_counter++;
2837				}
2838			}
2839
2840			/* make sure to "ignore" the items that we handled above */
2841			if ((!isset($graph_group_array[$item['id']])) && (!isset($target_graph_group_array[$item['id']]))) {
2842				db_execute_prepared('UPDATE graph_templates_item
2843					SET sequence = ?
2844					WHERE id = ?',
2845					array($sequence_counter, $item['id']));
2846
2847				$sequence_counter++;
2848			}
2849		}
2850	}
2851}
2852
2853/**
2854 * get_graph_group - returns an array containing each item in the graph group given a single
2855 * graph item in that group
2856 *
2857 * @param $graph_template_item_id - (int) the ID of the graph item to return the group of
2858 *
2859 * @return - (array) an array containing each item in the graph group
2860 */
2861function get_graph_group($graph_template_item_id) {
2862	global $graph_item_types;
2863
2864	$graph_item = db_fetch_row_prepared('SELECT graph_type_id, sequence, local_graph_id, graph_template_id
2865		FROM graph_templates_item
2866		WHERE id = ?',
2867		array($graph_template_item_id));
2868
2869	$params[] = $graph_item['sequence'];
2870
2871	if (empty($graph_item['local_graph_id'])) {
2872		$params[] = $graph_item['graph_template_id'];
2873		$sql_where = 'graph_template_id = ? AND local_graph_id = 0';
2874	} else {
2875		$params[] = $graph_item['sequence'];
2876		$sql_where = 'local_graph_id = ?';
2877	}
2878
2879	/* parents are LINE%, AREA%, and STACK%. If not return */
2880	if (!preg_match('/(LINE|AREA|STACK)/', $graph_item_types[$graph_item['graph_type_id']])) {
2881		return array();
2882	}
2883
2884	$graph_item_children_array = array();
2885
2886	/* put the parent item in the array as well */
2887	$graph_item_children_array[$graph_template_item_id] = $graph_template_item_id;
2888
2889	$graph_items = db_fetch_assoc_prepared("SELECT id, graph_type_id, text_format, hard_return
2890		FROM graph_templates_item
2891		WHERE sequence > ?
2892		AND $sql_where
2893		ORDER BY sequence",
2894		$params);
2895
2896	$is_hard = false;
2897
2898	if (cacti_sizeof($graph_items)) {
2899		foreach ($graph_items as $item) {
2900			if ($is_hard) {
2901				return $graph_item_children_array;
2902			} elseif (strstr($graph_item_types[$item['graph_type_id']], 'GPRINT') !== false) {
2903				/* a child must be a GPRINT */
2904				$graph_item_children_array[$item['id']] = $item['id'];
2905
2906				if ($item['hard_return'] == 'on') {
2907					$is_hard = true;
2908				}
2909			} elseif (strstr($graph_item_types[$item['graph_type_id']], 'COMMENT') !== false) {
2910				if (preg_match_all('/\|([0-9]{1,2}):(bits|bytes):(\d):(current|total|max|total_peak|all_max_current|all_max_peak|aggregate_max|aggregate_sum|aggregate_current|aggregate):(\d)?\|/', $item['text_format'], $matches, PREG_SET_ORDER)) {
2911					$graph_item_children_array[$item['id']] = $item['id'];
2912				} elseif (preg_match_all('/\|sum:(\d|auto):(current|total|atomic):(\d):(\d+|auto)\|/', $item['text_format'], $matches, PREG_SET_ORDER)) {
2913					$graph_item_children_array[$item['id']] = $item['id'];
2914				} else {
2915					/* if not a GPRINT or special COMMENT then get out */
2916					return $graph_item_children_array;
2917				}
2918			} else {
2919				/* if not a GPRINT or special COMMENT then get out */
2920				return $graph_item_children_array;
2921			}
2922		}
2923	}
2924
2925	return $graph_item_children_array;
2926}
2927
2928/**
2929 * get_graph_parent - returns the ID of the next or previous parent graph item id
2930 *
2931 * @param $graph_template_item_id - the ID of the current graph item
2932 * @param $direction - ('next' or 'previous') whether to find the next or previous parent
2933 *
2934 * @return - the ID of the next or previous parent graph item id
2935 */
2936function get_graph_parent($graph_template_item_id, $direction) {
2937	$graph_item = db_fetch_row_prepared('SELECT sequence, local_graph_id, graph_template_id
2938		FROM graph_templates_item
2939		WHERE id = ?',
2940		array($graph_template_item_id));
2941
2942	if (empty($graph_item['local_graph_id'])) {
2943		$sql_where = 'graph_template_id = ' . $graph_item['graph_template_id'] . ' AND local_graph_id = 0';
2944	} else {
2945		$sql_where = 'local_graph_id = ' . $graph_item['local_graph_id'];
2946	}
2947
2948	if ($direction == 'next') {
2949		$sql_operator = '>';
2950		$sql_order = 'ASC';
2951	} elseif ($direction == 'previous') {
2952		$sql_operator = '<';
2953		$sql_order = 'DESC';
2954	}
2955
2956	$next_parent_id = db_fetch_cell("SELECT id
2957		FROM graph_templates_item
2958		WHERE sequence $sql_operator " . $graph_item['sequence'] . "
2959		AND graph_type_id IN (4, 5, 6, 7, 8, 20)
2960		AND $sql_where
2961		ORDER BY sequence $sql_order
2962		LIMIT 1");
2963
2964	if (empty($next_parent_id)) {
2965		return 0;
2966	} else {
2967		return $next_parent_id;
2968	}
2969}
2970
2971/**
2972 * get_item - returns the ID of the next or previous item id
2973 *
2974 * @param $tblname - the table name that contains the target id
2975 * @param $field - the field name that contains the target id
2976 * @param $startid - (int) the current id
2977 * @param $lmt_query - an SQL "where" clause to limit the query
2978 * @param $direction - ('next' or 'previous') whether to find the next or previous item id
2979 *
2980 * @return - (int) the ID of the next or previous item id
2981 */
2982function get_item($tblname, $field, $startid, $lmt_query, $direction) {
2983	if ($direction == 'next') {
2984		$sql_operator = '>';
2985		$sql_order = 'ASC';
2986	} elseif ($direction == 'previous') {
2987		$sql_operator = '<';
2988		$sql_order = 'DESC';
2989	}
2990
2991	$current_sequence = db_fetch_cell_prepared("SELECT $field
2992		FROM $tblname
2993		WHERE id = ?",
2994		array($startid));
2995
2996	$new_item_id = db_fetch_cell("SELECT id
2997		FROM $tblname
2998		WHERE $field $sql_operator $current_sequence " . ($lmt_query != '' ? " AND $lmt_query":"") . "
2999		ORDER BY $field $sql_order
3000		LIMIT 1");
3001
3002	if (empty($new_item_id)) {
3003		return $startid;
3004	} else {
3005		return $new_item_id;
3006	}
3007}
3008
3009/**
3010 * get_sequence - returns the next available sequence id
3011 *
3012 * @param $id - (int) the current id
3013 * @param $field - the field name that contains the target id
3014 * @param $table_name - the table name that contains the target id
3015 * @param $group_query - an SQL "where" clause to limit the query
3016 *
3017 * @return - (int) the next available sequence id
3018 */
3019function get_sequence($id, $field, $table_name, $group_query) {
3020	if (empty($id)) {
3021		$data = db_fetch_row("SELECT max($field)+1 AS seq
3022			FROM $table_name
3023			WHERE $group_query");
3024
3025		if ($data['seq'] == '') {
3026			return 1;
3027		} else {
3028			return $data['seq'];
3029		}
3030	} else {
3031		$data = db_fetch_row_prepared("SELECT $field
3032			FROM $table_name
3033			WHERE id = ?",
3034			array($id));
3035
3036		return $data[$field];
3037	}
3038}
3039
3040/**
3041 * move_item_down - moves an item down by swapping it with the item below it
3042 *
3043 * @param $table_name - the table name that contains the target id
3044 * @param $current_id - (int) the current id
3045 * @param $group_query - an SQL "where" clause to limit the query
3046 */
3047function move_item_down($table_name, $current_id, $group_query = '') {
3048	$next_item = get_item($table_name, 'sequence', $current_id, $group_query, 'next');
3049
3050	$sequence = db_fetch_cell_prepared("SELECT sequence
3051		FROM $table_name
3052		WHERE id = ?",
3053		array($current_id));
3054
3055	$sequence_next = db_fetch_cell_prepared("SELECT sequence
3056		FROM $table_name
3057		WHERE id = ?",
3058		array($next_item));
3059
3060	db_execute_prepared("UPDATE $table_name
3061		SET sequence = ?
3062		WHERE id = ?",
3063		array($sequence_next, $current_id));
3064
3065	db_execute_prepared("UPDATE $table_name
3066		SET sequence = ?
3067		WHERE id = ?",
3068		array($sequence, $next_item));
3069}
3070
3071/**
3072 * move_item_up - moves an item down by swapping it with the item above it
3073 *
3074 * @param $table_name - the table name that contains the target id
3075 * @param $current_id - (int) the current id
3076 * @param $group_query - an SQL "where" clause to limit the query
3077 */
3078function move_item_up($table_name, $current_id, $group_query = '') {
3079	$last_item = get_item($table_name, 'sequence', $current_id, $group_query, 'previous');
3080
3081	$sequence = db_fetch_cell_prepared("SELECT sequence
3082		FROM $table_name
3083		WHERE id = ?",
3084		array($current_id));
3085
3086	$sequence_last = db_fetch_cell_prepared("SELECT sequence
3087		FROM $table_name
3088		WHERE id = ?",
3089		array($last_item));
3090
3091	db_execute_prepared("UPDATE $table_name
3092		SET sequence = ?
3093		WHERE id = ?",
3094		array($sequence_last, $current_id));
3095
3096	db_execute_prepared("UPDATE $table_name
3097		SET sequence = ?
3098		WHERE id = ?",
3099		array($sequence, $last_item));
3100}
3101
3102/**
3103 * exec_into_array - executes a command and puts each line of its output into
3104 * an array
3105 *
3106 * @param $command_line - the command to execute
3107 *
3108 * @return - (array) an array containing the command output
3109 */
3110function exec_into_array($command_line) {
3111	$out = array();
3112	$err = 0;
3113	exec($command_line,$out,$err);
3114
3115	return array_values($out);
3116}
3117
3118/**
3119 * get_web_browser - determines the current web browser in use by the client
3120 *
3121 * @return - ('ie' or 'moz' or 'other')
3122 */
3123function get_web_browser() {
3124	if (!empty($_SERVER['HTTP_USER_AGENT'])) {
3125		if (stristr($_SERVER['HTTP_USER_AGENT'], 'Mozilla') && (!(stristr($_SERVER['HTTP_USER_AGENT'], 'compatible')))) {
3126			return 'moz';
3127		} elseif (stristr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
3128			return 'ie';
3129		} else {
3130			return 'other';
3131		}
3132	} else {
3133		return 'other';
3134	}
3135}
3136
3137function get_guest_account() {
3138	return db_fetch_cell_prepared('SELECT id
3139		FROM user_auth
3140		WHERE username = ? OR id = ?',
3141		array(read_config_option('guest_user'), read_config_option('guest_user')));
3142}
3143
3144function get_template_account() {
3145	return db_fetch_cell_prepared('SELECT id
3146		FROM user_auth
3147		WHERE username = ? OR id = ?',
3148		array(read_config_option('user_template'), read_config_option('user_template')));
3149}
3150
3151/**
3152 * draw_login_status - provides a consistent login status page for all pages that use it
3153 */
3154function draw_login_status($using_guest_account = false) {
3155	global $config;
3156
3157	$guest_account = get_guest_account();
3158	$auth_method   = read_config_option('auth_method');
3159
3160	if (isset($_SESSION['sess_user_id']) && $_SESSION['sess_user_id'] == $guest_account) {
3161		api_plugin_hook('nav_login_before');
3162		print __('Logged in as') . " <span id='user' class='user usermenuup'>". __('guest') . "</span></div><div><ul class='menuoptions' style='display:none;'><li><a href='" . $config['url_path'] . "index.php?login=true'>" . __('Login as Regular User') . "</a></li>\n";
3163		print "<li class='menuHr'><hr class='menu'></li>";
3164		print "<li id='userCommunity'><a href='https://forums.cacti.net' target='_blank' rel='noopener'>" . __('User Community') . "</a></li>";
3165		print "<li id='userDocumentation'><a href='https://github.com/Cacti/documentation/blob/develop/README.md' target='_blank' rel='noopener'>" . __('Documentation') . "</a></li>";
3166		print "</ul>";
3167
3168		api_plugin_hook('nav_login_after');
3169	} elseif (isset($_SESSION['sess_user_id']) && $using_guest_account == false) {
3170		$user = db_fetch_row_prepared('SELECT username, password_change, realm FROM user_auth WHERE id = ?', array($_SESSION['sess_user_id']));
3171		api_plugin_hook('nav_login_before');
3172
3173		print __('Logged in as') . " <span id='user' class='user usermenuup'>" . html_escape($user['username']) .
3174			"</span></div><div><ul class='menuoptions' style='display:none;'>";
3175
3176		print "<li><a href='#' class='loggedInAs' style='display:none;'>" . __esc('Logged in as %s', $user['username']) . "</a></li><hr class='menu'>";
3177
3178		print (is_realm_allowed(20) ? "<li><a href='" . html_escape($config['url_path'] . 'auth_profile.php?action=edit') . "'>" . __('Edit Profile') . '</a></li>':'');
3179		print ($user['password_change'] == 'on' && $user['realm'] == 0 ? "<li><a href='" . html_escape($config['url_path'] . 'auth_changepassword.php') . "'>" . __('Change Password') . '</a></li>':'');
3180		print ((is_realm_allowed(20) || ($user['password_change'] == 'on' && $user['realm'] == 0)) ? "<li class='menuHr'><hr class='menu'></li>":'');
3181		if (is_realm_allowed(28)) {
3182			print "<li id='userCommunity'><a href='https://forums.cacti.net' target='_blank' rel='noopener'>" . __('User Community') . '</a></li>';
3183			print "<li id='userDocumentation'><a href='https://github.com/Cacti/documentation/blob/develop/README.md' target='_blank' rel='noopener'>" . __('Documentation') . '</a></li>';
3184			print "<li class='menuHr'><hr class='menu'></li>";
3185		}
3186		print ($auth_method > 0 && $auth_method != 2 ? "<li><a href='" . html_escape($config['url_path'] . 'logout.php') . "'>" . __('Logout') . '</a></li>':'');
3187		print '</ul>';
3188
3189		api_plugin_hook('nav_login_after');
3190	}
3191}
3192
3193/**
3194 * draw_navigation_text - determines the top header navigation text for the current page and displays it to
3195 *
3196 * @param $type - Either 'url' or 'title'
3197 *
3198 * @return - Either the navigation text or title
3199 */
3200function draw_navigation_text($type = 'url') {
3201	global $config, $navigation;
3202
3203	$nav_level_cache = (isset($_SESSION['sess_nav_level_cache']) ? $_SESSION['sess_nav_level_cache'] : array());
3204	$navigation      = api_plugin_hook_function('draw_navigation_text', $navigation);
3205	$current_page    = get_current_page();
3206
3207	// Do an error check here for bad plugins manipulating the cache
3208	if (!is_array($nav_level_cache)) {
3209		$nav_level_cache = array();
3210	}
3211
3212	if (!isempty_request_var('action')) {
3213		get_filter_request_var('action', FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => '/^([-a-zA-Z0-9_\s]+)$/')));
3214	}
3215
3216	$current_action = (isset_request_var('action') ? get_request_var('action') : '');
3217
3218	// find the current page in the big array
3219	if (isset($navigation[$current_page . ':' . $current_action])) {
3220		$current_array = $navigation[$current_page . ':' . $current_action];
3221	} else {
3222		$current_array = array(
3223			'mapping' => 'index.php:',
3224			'title' => ucwords(str_replace('_', ' ', basename(get_current_page(), '.php'))),
3225			'level' => 1
3226		);
3227	}
3228
3229	if (isset($current_array['mapping'])) {
3230		$current_mappings = explode(',', $current_array['mapping']);
3231	} else {
3232		$current_mappings = array();
3233	}
3234
3235	$current_nav = "<ul id='breadcrumbs'>";
3236	$title       = '';
3237	$nav_count   = 0;
3238
3239	// resolve all mappings to build the navigation string
3240	for ($i=0; ($i < cacti_count($current_mappings)); $i++) {
3241		if (empty($current_mappings[$i])) {
3242			continue;
3243		}
3244
3245		if  ($i == 0) {
3246			// always use the default for level == 0
3247			$url = $navigation[basename($current_mappings[$i])]['url'];
3248
3249			if (basename($url) == 'graph_view.php') continue;
3250		} elseif (isset($nav_level_cache[$i]) && !empty($nav_level_cache[$i]['url'])) {
3251			// found a match in the url cache for this level
3252			$url = $nav_level_cache[$i]['url'];
3253		} elseif (isset($current_array['url'])) {
3254			// found a default url in the above array
3255			$url = $current_array['url'];
3256		} else {
3257			// default to no url
3258			$url = '';
3259		}
3260
3261		if ($current_mappings[$i] == '?') {
3262			// '?' tells us to pull title from the cache at this level
3263			if (isset($nav_level_cache[$i])) {
3264				$current_nav .= (empty($url) ? '' : "<li><a id='nav_$i' href='" . html_escape($url) . "'>");
3265				$current_nav .= html_escape(resolve_navigation_variables($navigation[$nav_level_cache[$i]['id']]['title']));
3266				$current_nav .= (empty($url) ? '' : '</a>' . (get_selected_theme() == 'classic' ? ' -> ':'') . '</li>');
3267				$title .= html_escape(resolve_navigation_variables($navigation[$nav_level_cache[$i]['id']]['title'])) . ' -> ';
3268			}
3269		} else {
3270			// there is no '?' - pull from the above array
3271			$current_nav .= (empty($url) ? '' : "<li><a id='nav_$i' href='" . html_escape($url) . "'>");
3272			$current_nav .= html_escape(resolve_navigation_variables($navigation[basename($current_mappings[$i])]['title']));
3273			$current_nav .= (empty($url) ? '' : '</a>' . (get_selected_theme() == 'classic' ? ' -> ':'') . '</li>');
3274			$title .= html_escape(resolve_navigation_variables($navigation[basename($current_mappings[$i])]['title'])) . ' -> ';
3275		}
3276
3277		$nav_count++;
3278	}
3279
3280	if ($nav_count) {
3281		if (isset($current_array['title'])) {
3282			$current_nav .= "<li><a id='nav_$i' href=#>" . html_escape(resolve_navigation_variables($current_array['title'])) . '</a></li>';
3283		}
3284	} else {
3285		$current_array = $navigation[$current_page . ':' . $current_action];
3286		$url           = (isset($current_array['url']) ? $current_array['url']:'');
3287
3288		if (isset($current_array['title'])) {
3289			$current_nav  .= "<li><a id='nav_$i' href='$url'>" . html_escape(resolve_navigation_variables($current_array['title'])) . '</a></li>';
3290		}
3291	}
3292
3293	if (isset_request_var('action') || get_nfilter_request_var('action') == 'tree_content') {
3294		$tree_id = 0;
3295		$leaf_id = 0;
3296
3297		if (isset_request_var('node')) {
3298			$parts = explode('-', get_request_var('node'));
3299
3300			// Check for tree anchor
3301			if (strpos(get_request_var('node'), 'tree_anchor') !== false) {
3302				$tree_id = $parts[1];
3303				$leaf_id = 0;
3304			} elseif (strpos(get_request_var('node'), 'tbranch') !== false) {
3305				// Check for branch
3306				$leaf_id = $parts[1];
3307				$tree_id = db_fetch_cell_prepared('SELECT graph_tree_id
3308					FROM graph_tree_items
3309					WHERE id = ?',
3310					array($leaf_id));
3311			}
3312		}
3313
3314		if ($leaf_id > 0) {
3315			$leaf = db_fetch_row_prepared('SELECT host_id, title, graph_tree_id
3316				FROM graph_tree_items
3317				WHERE id = ?',
3318				array($leaf_id));
3319
3320			if (cacti_sizeof($leaf)) {
3321				if ($leaf['host_id'] > 0) {
3322					$leaf_name = db_fetch_cell_prepared('SELECT description
3323						FROM host
3324						WHERE id = ?',
3325						array($leaf['host_id']));
3326				} else {
3327					$leaf_name = $leaf['title'];
3328				}
3329
3330				$tree_name = db_fetch_cell_prepared('SELECT name
3331					FROM graph_tree
3332					WHERE id = ?',
3333					array($leaf['graph_tree_id']));
3334			} else {
3335				$leaf_name = __('Leaf');
3336				$tree_name = '';
3337			}
3338
3339			if (isset_request_var('hgd') && get_nfilter_request_var('hgd') != '') {
3340				$parts = explode(':', get_nfilter_request_var('hgd'));
3341				input_validate_input_number($parts[1]);
3342
3343				if ($parts[0] == 'gt') {
3344					$leaf_sub = db_fetch_cell_prepared('SELECT name
3345						FROM graph_templates
3346						WHERE id = ?',
3347						array($parts[1]));
3348				} else {
3349					if ($parts[1] > 0) {
3350						$leaf_sub = db_fetch_cell_prepared('SELECT name
3351							FROM snmp_query
3352							WHERE id = ?',
3353							array($parts[1]));
3354					} else {
3355						$leaf_sub = __('Non Query Based');
3356					}
3357				}
3358			} else {
3359				$leaf_sub = '';
3360			}
3361		} else {
3362			$leaf_name = '';
3363			$leaf_sub  = '';
3364
3365			if ($tree_id > 0) {
3366				$tree_name = db_fetch_cell_prepared('SELECT name
3367					FROM graph_tree
3368					WHERE id = ?',
3369					array($tree_id));
3370			} else {
3371				$tree_name = '';
3372			}
3373		}
3374
3375		$tree_title = $tree_name . ($leaf_name != '' ? ' (' . trim($leaf_name):'') . ($leaf_sub != '' ? ':' . trim($leaf_sub) . ')':($leaf_name != '' ? ')':''));
3376
3377		if ($tree_title != '') {
3378			$current_nav .= "<li><a id='nav_title' href=#>" . html_escape($tree_title) . '</a></li>';
3379		}
3380	} elseif (preg_match('#link.php\?id=(\d+)#', $_SERVER['REQUEST_URI'], $matches)) {
3381		$externalLinks = db_fetch_row_prepared('SELECT title, style FROM external_links WHERE id = ?', array($matches[1]));
3382		$title = $externalLinks['title'];
3383		$style = $externalLinks['style'];
3384
3385		if ($style == 'CONSOLE') {
3386			$current_nav = "<ul id='breadcrumbs'><li><a id='nav_0' href='" . $config['url_path'] .
3387				"index.php'>" . __('Console') . '</a>' . (get_selected_theme() == 'classic' ? ' -> ':'') . '</li>';
3388			$current_nav .= "<li><a id='nav_1' href='#'>" . __('Link %s', html_escape($title)) . '</a></li>';
3389		} else {
3390			$current_nav = "<ul id='breadcrumbs'><li><a id='nav_0'>" . html_escape($title) . '</a></li>';
3391		}
3392		$tree_title = '';
3393	} else {
3394		$tree_title = '';
3395	}
3396
3397	if (isset($current_array['title'])) {
3398		$title .= html_escape(resolve_navigation_variables($current_array['title']) . ' ' . $tree_title);
3399	}
3400
3401	// keep a cache for each level we encounter
3402	$hasNavError = false;
3403	if (is_array($current_page)) {
3404		cacti_log('WARNING: Navigation item suppressed - current page is not a string: ' . var_export($current_page,true));
3405		$hasNavError = true;
3406	}
3407
3408	if (is_array($current_action)) {
3409		cacti_log('WARNING: Navigation item suppressed - current action is not a string: '. var_export($current_action,true));
3410		$hasNavError = true;
3411	}
3412
3413	if (is_array($current_array['level'])) {
3414		cacti_log('WARNING: Navigation item suppressed - current level is not a string: ' . var_export($current_array['level'],true));
3415		$hasNavError = true;
3416	}
3417
3418	if (!$hasNavError) {
3419		$nav_level_cache[$current_array['level']] = array(
3420			'id' => $current_page . ':' . $current_action,
3421			'url' => get_browser_query_string()
3422		);
3423	}
3424	$current_nav .= '</ul>';
3425
3426	$_SESSION['sess_nav_level_cache'] = $nav_level_cache;
3427
3428	if ($type == 'url') {
3429		return $current_nav;
3430	} else {
3431		return $title;
3432	}
3433}
3434
3435/**
3436 * resolve_navigation_variables - substitute any variables contained in the navigation text
3437 *
3438 * @param $text - the text to substitute in
3439 *
3440 * @return - the original navigation text with all substitutions made
3441 */
3442function resolve_navigation_variables($text) {
3443	$graphTitle = get_graph_title(get_filter_request_var('local_graph_id'));
3444
3445	if (preg_match_all("/\|([a-zA-Z0-9_]+)\|/", $text, $matches)) {
3446		for ($i=0; $i<cacti_count($matches[1]); $i++) {
3447			switch ($matches[1][$i]) {
3448				case 'current_graph_title':
3449					$text = str_replace('|' . $matches[1][$i] . '|', $graphTitle, $text);
3450					break;
3451			}
3452		}
3453	}
3454
3455	return $text;
3456}
3457
3458/**
3459 * get_associated_rras - returns a list of all RRAs referenced by a particular graph
3460 *
3461 * @param $local_graph_id - (int) the ID of the graph to retrieve a list of RRAs for
3462 *
3463 * @return - (array) an array containing the name and id of each RRA found
3464 */
3465function get_associated_rras($local_graph_id, $sql_where = '') {
3466	return db_fetch_assoc_prepared('SELECT DISTINCT ' . SQL_NO_CACHE . "
3467		dspr.id, dsp.step, dspr.steps, dspr.rows, dspr.name, dtd.rrd_step, dspr.timespan
3468		FROM graph_templates_item AS gti
3469		LEFT JOIN data_template_rrd AS dtr
3470		ON gti.task_item_id=dtr.id
3471		LEFT JOIN data_template_data AS dtd
3472		ON dtr.local_data_id = dtd.local_data_id
3473		LEFT JOIN data_source_profiles AS dsp
3474		ON dtd.data_source_profile_id=dsp.id
3475		LEFT JOIN data_source_profiles_rra AS dspr
3476		ON dsp.id=dspr.data_source_profile_id
3477		AND dtd.local_data_id != 0
3478		WHERE gti.local_graph_id = ?
3479		$sql_where
3480		ORDER BY dspr.steps",
3481		array($local_graph_id)
3482	);
3483}
3484
3485/**
3486 * get_nearest_timespan - returns the nearest defined timespan.  Used for adding a default
3487 * graph timespan for data source profile rras.
3488 *
3489 * @param $timespan - (int) the timespan to fine a default for
3490 *
3491 * @return - (int) the timespan to apply for the data source profile rra value
3492 */
3493function get_nearest_timespan($timespan) {
3494	global $timespans;
3495
3496	$last = end($timespans);
3497
3498	foreach($timespans as $index => $name) {
3499		if ($timespan > $index) {
3500			$last = $index;
3501			continue;
3502		} elseif ($timespan == $index) {
3503			return $index;
3504		} else {
3505			return $last;
3506		}
3507	}
3508
3509	return $last;
3510}
3511
3512/**
3513 * get_browser_query_string - returns the full url, including args requested by the browser
3514 *
3515 * @return - the url requested by the browser
3516 */
3517function get_browser_query_string() {
3518	if (!empty($_SERVER['REQUEST_URI'])) {
3519		return sanitize_uri($_SERVER['REQUEST_URI']);
3520	} else {
3521		return sanitize_uri(get_current_page() . (empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING']));
3522	}
3523}
3524
3525/**
3526 * get_current_page - returns the basename of the current page in a web server friendly way
3527 *
3528 * @return - the basename of the current script file
3529 */
3530function get_current_page($basename = true) {
3531	if (isset($_SERVER['SCRIPT_NAME']) && $_SERVER['SCRIPT_NAME'] != '') {
3532		if ($basename) {
3533			return basename($_SERVER['SCRIPT_NAME']);
3534		} else {
3535			return $_SERVER['SCRIPT_NAME'];
3536		}
3537	} elseif (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] != '') {
3538		if ($basename) {
3539			return basename($_SERVER['SCRIPT_FILENAME']);
3540		} else {
3541			return $_SERVER['SCRIPT_FILENAME'];
3542		}
3543	} else {
3544		cacti_log('ERROR: unable to determine current_page');
3545	}
3546
3547	return false;
3548}
3549
3550/**
3551 * get_hash_graph_template - returns the current unique hash for a graph template
3552 *
3553 * @param $graph_template_id - (int) the ID of the graph template to return a hash for
3554 * @param $sub_type (optional) return the hash for a particular subtype of this type
3555 *
3556 * @return - a 128-bit, hexadecimal hash
3557 */
3558function get_hash_graph_template($graph_template_id, $sub_type = 'graph_template') {
3559	switch ($sub_type) {
3560		case 'graph_template':
3561			$hash = db_fetch_cell_prepared('SELECT hash FROM graph_templates WHERE id = ?', array($graph_template_id));
3562			break;
3563		case 'graph_template_item':
3564			$hash = db_fetch_cell_prepared('SELECT hash FROM graph_templates_item WHERE id = ?', array($graph_template_id));
3565			break;
3566		case 'graph_template_input':
3567			$hash = db_fetch_cell_prepared('SELECT hash FROM graph_template_input WHERE id = ?', array($graph_template_id));
3568			break;
3569		default:
3570			return generate_hash();
3571			break;
3572	}
3573
3574	if (preg_match('/[a-fA-F0-9]{32}/', $hash)) {
3575		return $hash;
3576	} else {
3577		return generate_hash();
3578	}
3579}
3580
3581/**
3582 * get_hash_data_template - returns the current unique hash for a data template
3583 *
3584 * @param $graph_template_id - (int) the ID of the data template to return a hash for
3585 * @param $sub_type (optional) return the hash for a particular subtype of this type
3586 *
3587 * @return - a 128-bit, hexadecimal hash
3588 */
3589function get_hash_data_template($data_template_id, $sub_type = 'data_template') {
3590	switch ($sub_type) {
3591		case 'data_template':
3592			$hash = db_fetch_cell_prepared('SELECT hash FROM data_template WHERE id = ?', array($data_template_id));
3593			break;
3594		case 'data_template_item':
3595			$hash = db_fetch_cell_prepared('SELECT hash FROM data_template_rrd WHERE id = ?', array($data_template_id));
3596			break;
3597		default:
3598			return generate_hash();
3599			break;
3600	}
3601
3602	if (preg_match('/[a-fA-F0-9]{32}/', $hash)) {
3603		return $hash;
3604	} else {
3605		return generate_hash();
3606	}
3607}
3608
3609/**
3610 * get_hash_data_input - returns the current unique hash for a data input method
3611 *
3612 * @param $graph_template_id - (int) the ID of the data input method to return a hash for
3613 * @param $sub_type (optional) return the hash for a particular subtype of this type
3614 *
3615 * @return - a 128-bit, hexadecimal hash
3616 */
3617function get_hash_data_input($data_input_id, $sub_type = 'data_input_method') {
3618	switch ($sub_type) {
3619		case 'data_input_method':
3620			$hash = db_fetch_cell_prepared('SELECT hash FROM data_input WHERE id = ?', array($data_input_id));
3621			break;
3622		case 'data_input_field':
3623			$hash = db_fetch_cell_prepared('SELECT hash FROM data_input_fields WHERE id = ?', array($data_input_id));
3624			break;
3625		default:
3626			return generate_hash();
3627			break;
3628	}
3629
3630	if (preg_match('/[a-fA-F0-9]{32}/', $hash)) {
3631		return $hash;
3632	} else {
3633		return generate_hash();
3634	}
3635}
3636
3637/**
3638 * get_hash_cdef - returns the current unique hash for a cdef
3639 *
3640 * @param $graph_template_id - (int) the ID of the cdef to return a hash for
3641 * @param $sub_type (optional) return the hash for a particular subtype of this type
3642 *
3643 * @return - a 128-bit, hexadecimal hash
3644 */
3645function get_hash_cdef($cdef_id, $sub_type = 'cdef') {
3646	if (!is_numeric($cdef_id)) {
3647		return generate_hash();
3648	}
3649
3650	switch ($sub_type) {
3651		case 'cdef':
3652			$hash = db_fetch_cell_prepared('SELECT hash FROM cdef WHERE id = ?', array($cdef_id));
3653			break;
3654		case 'cdef_item':
3655			$hash = db_fetch_cell_prepared('SELECT hash FROM cdef_items WHERE id = ?', array($cdef_id));
3656			break;
3657		default:
3658			return generate_hash();
3659			break;
3660	}
3661
3662	if (strlen($hash) == 32 && ctype_xdigit($hash)) {
3663		return $hash;
3664	} else {
3665		return generate_hash();
3666	}
3667}
3668
3669/**
3670 * get_hash_gprint - returns the current unique hash for a gprint preset
3671 *
3672 * @param $graph_template_id - (int) the ID of the gprint preset to return a hash for
3673 *
3674 * @return - a 128-bit, hexadecimal hash
3675 */
3676function get_hash_gprint($gprint_id) {
3677	$hash = db_fetch_cell_prepared('SELECT hash FROM graph_templates_gprint WHERE id = ?', array($gprint_id));
3678
3679	if (strlen($hash) == 32 && ctype_xdigit($hash)) {
3680		return $hash;
3681	} else {
3682		return generate_hash();
3683	}
3684}
3685
3686/**
3687 * returns the current unique hash for a vdef
3688 *
3689 * @param $graph_template_id - the ID of the vdef to return a hash for
3690 * @param $sub_type          - return the hash for a particular subtype of this type
3691 *
3692 * @return - a 128-bit, hexadecimal hash
3693 */
3694function get_hash_vdef($vdef_id, $sub_type = "vdef") {
3695	switch ($sub_type) {
3696		case 'vdef':
3697			$hash = db_fetch_cell_prepared('SELECT hash FROM vdef WHERE id = ?', array($vdef_id));
3698			break;
3699		case 'vdef_item':
3700			$hash = db_fetch_cell_prepared('SELECT hash FROM vdef_items WHERE id = ?', array($vdef_id));
3701			break;
3702		default:
3703			return generate_hash();
3704			break;
3705	}
3706
3707	if (strlen($hash) == 32 && ctype_xdigit($hash)) {
3708		return $hash;
3709	} else {
3710		return generate_hash();
3711	}
3712}
3713
3714/**
3715 * get_hash_data_source_profile - returns the current unique hash for a vdef
3716 *
3717 * @param $data_source_profile_id - the ID of the data_source_profile to return a hash for
3718 *
3719 * @return - a 128-bit, hexadecimal hash
3720 */
3721function get_hash_data_source_profile($data_source_profile_id) {
3722	$hash = db_fetch_cell_prepared('SELECT hash FROM data_source_profiles WHERE id = ?', array($data_source_profile_id));
3723
3724	if (strlen($hash) == 32 && ctype_xdigit($hash)) {
3725		return $hash;
3726	} else {
3727		return generate_hash();
3728	}
3729}
3730
3731/**
3732 * get_hash_host_template - returns the current unique hash for a gprint preset
3733 *
3734 * @param $host_template_id - the ID of the host template to return a hash for
3735 *
3736 * @return - a 128-bit, hexadecimal hash
3737 */
3738function get_hash_host_template($host_template_id) {
3739	$hash = db_fetch_cell_prepared('SELECT hash FROM host_template WHERE id = ?', array($host_template_id));
3740
3741	if (strlen($hash) == 32 && ctype_xdigit($hash)) {
3742		return $hash;
3743	} else {
3744		return generate_hash();
3745	}
3746}
3747
3748/**
3749 * get_hash_data_query - returns the current unique hash for a data query
3750 *
3751 * @param $graph_template_id - the ID of the data query to return a hash for
3752 * @param $sub_type return the hash for a particular subtype of this type
3753 *
3754 * @return - a 128-bit, hexadecimal hash
3755 */
3756function get_hash_data_query($data_query_id, $sub_type = 'data_query') {
3757	switch ($sub_type) {
3758		case 'data_query':
3759			$hash = db_fetch_cell_prepared('SELECT hash FROM snmp_query WHERE id = ?', array($data_query_id));
3760			break;
3761		case 'data_query_graph':
3762			$hash = db_fetch_cell_prepared('SELECT hash FROM snmp_query_graph WHERE id = ?', array($data_query_id));
3763			break;
3764		case 'data_query_sv_data_source':
3765			$hash = db_fetch_cell_prepared('SELECT hash FROM snmp_query_graph_rrd_sv WHERE id = ?', array($data_query_id));
3766			break;
3767		case 'data_query_sv_graph':
3768			$hash = db_fetch_cell_prepared('SELECT hash FROM snmp_query_graph_sv WHERE id = ?', array($data_query_id));
3769			break;
3770		default:
3771			return generate_hash();
3772			break;
3773	}
3774
3775	if (strlen($hash) == 32 && ctype_xdigit($hash)) {
3776		return $hash;
3777	} else {
3778		return generate_hash();
3779	}
3780}
3781
3782/**
3783 * get_hash_version - returns the item type and cacti version in a hash format
3784 *
3785 * @param $type - the type of item to represent ('graph_template','data_template',
3786 *   'data_input_method','cdef','vdef','gprint_preset','data_query','host_template')
3787 *
3788 * @return - a 24-bit hexadecimal hash (8-bits for type, 16-bits for version)
3789 */
3790function get_hash_version($type) {
3791	global $hash_type_codes, $cacti_version_codes, $config;
3792
3793	return $hash_type_codes[$type] . $cacti_version_codes[CACTI_VERSION];
3794}
3795
3796/**
3797 * generate_hash - generates a new unique hash
3798 *
3799 * @return - a 128-bit, hexadecimal hash
3800 */
3801function generate_hash() {
3802	return md5(session_id() . microtime() . rand(0,1000));
3803}
3804
3805/**
3806 * debug_log_insert_section_start - creates a header item for breaking down the debug log
3807 *
3808 * @param $type - the 'category' or type of debug message
3809 * @param $text - section header
3810 */
3811function debug_log_insert_section_start($type, $text, $allowcopy = false) {
3812	$copy_prefix = '';
3813	$copy_dataid = '';
3814	if ($allowcopy) {
3815		$uid = generate_hash();
3816		$copy_prefix   = '<div class=\'cactiTableButton debug\'><span><a class=\'linkCopyDark cactiTableCopy\' id=\'copyToClipboard' . $uid . '\'>' . __esc('Copy') . '</a></span></div>';
3817		$copy_dataid = ' id=\'clipboardData'.$uid.'\'';
3818		$copy_headerid = ' id=\'clipboardHeader'.$uid.'\'';
3819	}
3820
3821	debug_log_insert($type, '<table class=\'cactiTable debug\'' . $copy_headerid . '><tr class=\'tableHeader\'><td>' . html_escape($text) . $copy_prefix . '</td></tr><tr><td style=\'padding:0px;\'><table style=\'display:none;\'' . $copy_dataid . '><tr><td><div style=\'font-family: monospace;\'>');
3822}
3823
3824/**
3825 * debug_log_insert_section_end - finalizes the header started with the start function
3826 *
3827 * @param $type - the 'category' or type of debug message
3828 */
3829function debug_log_insert_section_end($type) {
3830	debug_log_insert($type, "</div></td></tr></table></td></tr></td></table>");
3831}
3832
3833/**
3834 * debug_log_insert - inserts a line of text into the debug log
3835 *
3836 * @param $type - the 'category' or type of debug message
3837 * @param $text - the actual debug message
3838 */
3839function debug_log_insert($type, $text) {
3840	global $config;
3841
3842	if ($config['poller_id'] == 1 || isset($_SESSION)) {
3843		if (!isset($_SESSION['debug_log'][$type])) {
3844			$_SESSION['debug_log'][$type] = array();
3845		}
3846
3847		array_push($_SESSION['debug_log'][$type], $text);
3848	} else {
3849		if (!isset($config['debug_log'][$type])) {
3850			$config['debug_log'][$type] = array();
3851		}
3852
3853		array_push($config['debug_log'][$type], $text);
3854	}
3855}
3856
3857/**
3858 * debug_log_clear - clears the debug log for a particular category
3859 *
3860 * @param $type - the 'category' to clear the debug log for. omitting this argument
3861 *   implies all categories
3862 */
3863function debug_log_clear($type = '') {
3864	if ($type == '') {
3865		kill_session_var('debug_log');
3866	} else {
3867		if (isset($_SESSION['debug_log'])) {
3868			unset($_SESSION['debug_log'][$type]);
3869		}
3870	}
3871}
3872
3873/**
3874 * debug_log_return - returns the debug log for a particular category
3875 *
3876 * @param $type - the 'category' to return the debug log for.
3877 *
3878 * @return - the full debug log for a particular category
3879 */
3880function debug_log_return($type) {
3881	$log_text = '';
3882
3883	if ($type == 'new_graphs') {
3884		if (isset($_SESSION['debug_log'][$type])) {
3885			$log_text .= "<table style='width:100%;'>";
3886			for ($i=0; $i<cacti_count($_SESSION['debug_log'][$type]); $i++) {
3887				$log_text .= '<tr><td>' . $_SESSION['debug_log'][$type][$i] . '</td></tr>';
3888			}
3889			$log_text .= '</table>';
3890		}
3891	} else {
3892		if (isset($_SESSION['debug_log'][$type])) {
3893			$log_text .= "<table style='width:100%;'>";
3894			foreach($_SESSION['debug_log'][$type] as $key => $val) {
3895				$log_text .= "<tr><td>$val</td></tr>\n";
3896				unset($_SESSION['debug_log'][$type][$key]);
3897			}
3898			$log_text .= '</table>';
3899		}
3900	}
3901
3902	return $log_text;
3903}
3904
3905/**
3906 * sanitize_search_string - cleans up a search string submitted by the user to be passed
3907 * to the database. NOTE: some of the code for this function came from the phpBB project.
3908 *
3909 * @param $string - the original raw search string
3910 *
3911 * @return - the sanitized search string
3912 */
3913function sanitize_search_string($string) {
3914	static $drop_char_match = array('(',')','^', '$', '<', '>', '`', '\'', '"', '|', ',', '?', '+', '[', ']', '{', '}', '#', ';', '!', '=', '*');
3915	static $drop_char_replace = array('','',' ', ' ', ' ', ' ', '', '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
3916
3917	/* Replace line endings by a space */
3918	$string = preg_replace('/[\n\r]/is', ' ', $string);
3919
3920	/* HTML entities like &nbsp; */
3921	$string = preg_replace('/\b&[a-z]+;\b/', ' ', $string);
3922
3923	/* Remove URL's */
3924	$string = preg_replace('/\b[a-z0-9]+:\/\/[a-z0-9\.\-]+(\/[a-z0-9\?\.%_\-\+=&\/]+)?/', ' ', $string);
3925
3926	/* Filter out strange characters like ^, $, &, change "it's" to "its" */
3927	for($i = 0; $i < cacti_count($drop_char_match); $i++) {
3928		$string =  str_replace($drop_char_match[$i], $drop_char_replace[$i], $string);
3929	}
3930
3931	return $string;
3932}
3933
3934/**
3935 * cleans up a URI, e.g. from REQUEST_URI and/or QUERY_STRING
3936 * in case of XSS attack, expect the result to be broken
3937 * we do NOT sanitize in a way, that attacks are converted to valid HTML
3938 * it is ok, when the result is broken but the application stays alive
3939 *
3940 * @param string $uri   - the uri to be sanitized
3941 *
3942 * @return string    - the sanitized uri
3943 */
3944function sanitize_uri($uri) {
3945	static $drop_char_match =   array('^', '$', '<', '>', '`', "'", '"', '|', '+', '[', ']', '{', '}', ';', '!', '(', ')');
3946	static $drop_char_replace = array( '', '',  '',  '',  '',  '',   '',  '',  '',  '',  '',  '',  '',  '',  '');
3947
3948	if (strpos($uri, 'graph_view.php')) {
3949		if (!strpos($uri, 'action=')) {
3950			$uri = $uri . (strpos($uri, '?') ? '&':'?') . 'action=' . get_nfilter_request_var('action');
3951		}
3952	}
3953
3954	return str_replace($drop_char_match, $drop_char_replace, strip_tags(urldecode($uri)));
3955}
3956
3957/**
3958 * Checks to see if a string is base64 encoded
3959 *
3960 * @param string $data   - the string to be validated
3961 *
3962 * @return boolean    - true is the string is base64 otherwise false
3963 */
3964function is_base64_encoded($data) {
3965	// Perform a simple check first
3966	if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $data)) {
3967		return false;
3968	}
3969
3970	// Now test with the built-in function
3971	$ndata = base64_decode($data, true);
3972	if ($ndata === false) {
3973		return false;
3974	}
3975
3976	// Do a re-encode test and compare
3977	if (base64_encode($ndata) != $data) {
3978		return false;
3979	}
3980
3981	return true;
3982}
3983
3984/**
3985 * cleans up a CDEF/VDEF string
3986 * the CDEF/VDEF must have passed all magic string replacements beforehand
3987 *
3988 * @param string $cdef   - the CDEF/VDEF to be sanitized
3989 *
3990 * @return string    - the sanitized CDEF/VDEF
3991 */
3992function sanitize_cdef($cdef) {
3993	static $drop_char_match =   array('^', '$', '<', '>', '`', '\'', '"', '|', '[', ']', '{', '}', ';', '!');
3994	static $drop_char_replace = array( '', '',  '',  '',  '',  '',   '',  '',  '',  '',  '',  '',  '',  '');
3995
3996	return str_replace($drop_char_match, $drop_char_replace, $cdef);
3997}
3998
3999/**
4000 * verifies all selected items are numeric to guard against injection
4001 *
4002 * @param array $items   - an array of serialized items from a post
4003 *
4004 * @return array      - the sanitized selected items array
4005 */
4006function sanitize_unserialize_selected_items($items) {
4007	if ($items != '') {
4008		$unstripped = stripslashes($items);
4009
4010		// validate that sanitized string is correctly formatted
4011		if (preg_match('/^a:[0-9]+:{/', $unstripped) && !preg_match('/(^|;|{|})O:\+?[0-9]+:"/', $unstripped)) {
4012			$items = unserialize($unstripped);
4013
4014			if (is_array($items)) {
4015				foreach ($items as $item) {
4016					if (is_array($item)) {
4017						return false;
4018					} elseif (!is_numeric($item) && ($item != '')) {
4019						return false;
4020					}
4021				}
4022			} else {
4023				return false;
4024			}
4025		} else {
4026			return false;
4027		}
4028	} else {
4029		return false;
4030	}
4031
4032	return $items;
4033}
4034
4035function cacti_escapeshellcmd($string) {
4036	global $config;
4037
4038	if ($config['cacti_server_os'] == 'unix') {
4039		return escapeshellcmd($string);
4040	} else {
4041		$replacements = "#&;`|*?<>^()[]{}$\\";
4042
4043		for ($i=0; $i < strlen($replacements); $i++) {
4044			$string = str_replace($replacements[$i], ' ', $string);
4045		}
4046		return $string;
4047	}
4048}
4049
4050
4051/**
4052 * mimics escapeshellarg, even for windows
4053 *
4054 * @param $string 	- the string to be escaped
4055 * @param $quote 	- true: do NOT remove quotes from result; false: do remove quotes
4056 *
4057 * @return			- the escaped [quoted|unquoted] string
4058 */
4059function cacti_escapeshellarg($string, $quote = true) {
4060	global $config;
4061
4062	if ($string == '') {
4063		return $string;
4064	}
4065
4066	/* we must use an apostrophe to escape community names under Unix in case the user uses
4067	characters that the shell might interpret. the ucd-snmp binaries on Windows flip out when
4068	you do this, but are perfectly happy with a quotation mark. */
4069	if ($config['cacti_server_os'] == 'unix') {
4070		$string = escapeshellarg($string);
4071		if ($quote) {
4072			return $string;
4073		} else {
4074			# remove first and last char
4075			return substr($string, 1, (strlen($string)-2));
4076		}
4077	} else {
4078		/* escapeshellarg takes care of different quotation for both linux and windows,
4079		 * but unfortunately, it blanks out percent signs
4080		 * we want to keep them, e.g. for GPRINT format strings
4081		 * so we need to create our own escapeshellarg
4082		 * on windows, command injection requires to close any open quotation first
4083		 * so we have to escape any quotation here */
4084		if (substr_count($string, CACTI_ESCAPE_CHARACTER)) {
4085			$string = str_replace(CACTI_ESCAPE_CHARACTER, "\\" . CACTI_ESCAPE_CHARACTER, $string);
4086		}
4087
4088		/* ... before we add our own quotation */
4089		if ($quote) {
4090			return CACTI_ESCAPE_CHARACTER . $string . CACTI_ESCAPE_CHARACTER;
4091		} else {
4092			return $string;
4093		}
4094	}
4095}
4096
4097/**
4098 * set a page refresh in Cacti through a callback
4099 *
4100 * @param $refresh - an array containing the page, seconds, and logout
4101 *
4102 * @return         - nill
4103 */
4104function set_page_refresh($refresh) {
4105	if (isset($refresh['seconds'])) {
4106		$_SESSION['refresh']['seconds'] = $refresh['seconds'];
4107	}
4108
4109	if (isset($refresh['logout'])) {
4110		if ($refresh['logout'] == 'true' || $refresh['logout'] === true) {
4111			$_SESSION['refresh']['logout'] = 'true';
4112		} else {
4113			$_SESSION['refresh']['logout'] = 'false';
4114		}
4115	} else {
4116		$_SESSION['refresh']['logout'] = 'true';
4117	}
4118
4119	if (isset($refresh['page'])) {
4120		$_SESSION['refresh']['page'] = $refresh['page'];
4121	}
4122}
4123
4124function bottom_footer() {
4125	global $config, $no_session_write;
4126
4127	include_once($config['base_path'] . '/include/global_session.php');
4128
4129	if (!isset_request_var('header') || get_nfilter_request_var('header') == 'true') {
4130		include_once($config['base_path'] . '/include/bottom_footer.php');
4131	}
4132
4133	/* we use this session var to store field values for when a save fails,
4134 	   this way we can restore the field's previous values. we reset it here, because
4135	   they only need to be stored for a single page
4136	*/
4137	kill_session_var('sess_field_values');
4138
4139	/* make sure the debug log doesn't get too big */
4140	debug_log_clear();
4141
4142	/* close the session */
4143	if (array_search(get_current_page(), $no_session_write) === false) {
4144		cacti_session_close();
4145	}
4146
4147	/* close the database connection */
4148	db_close();
4149}
4150
4151function top_header() {
4152	global $config;
4153
4154	if (!isset_request_var('header') || get_nfilter_request_var('header') == 'true') {
4155		include_once($config['base_path'] . '/include/top_header.php');
4156	}
4157}
4158
4159function top_graph_header() {
4160	global $config;
4161	if (!isset_request_var('header') || get_nfilter_request_var('header') == 'true') {
4162		include_once($config['base_path'] . '/include/top_graph_header.php');
4163	}
4164}
4165
4166function general_header() {
4167	global $config;
4168	if (!isset_request_var('header') || get_nfilter_request_var('header') == 'true') {
4169		include_once($config['base_path'] . '/include/top_general_header.php');
4170	}
4171}
4172
4173function appendHeaderSuppression($url) {
4174	if (strpos($url, 'header=false') < 0) {
4175		return $url . (strpos($url, '?') ? '&':'?') . 'header=false';
4176	}
4177
4178	return $url;
4179}
4180
4181function admin_email($subject, $message) {
4182	if (read_config_option('admin_user')) {
4183		if (read_config_option('notify_admin')) {
4184			$admin_details = db_fetch_row_prepared('SELECT full_name, email_address
4185				FROM user_auth
4186				WHERE id = ?',
4187				array(read_config_option('admin_user')));
4188
4189			if (cacti_sizeof($admin_details)) {
4190				$email = read_config_option('settings_from_email');
4191				$name  = read_config_option('settings_from_name');
4192
4193				if ($name != '') {
4194					$from = '"' . $name . '" <' . $email . '>';
4195				} else {
4196					$from = $email;
4197				}
4198
4199				if ($admin_details['email_address'] != '') {
4200					if ($admin_details['full_name'] != '') {
4201						$to = '"' . $admin_details['full_name'] . '" <' . $admin_details['email_address'] . '>';
4202					} else {
4203						$to = $admin_details['email_address'];
4204					}
4205
4206					send_mail($to, $from, $subject, $message, '', '', true);
4207				} else {
4208					cacti_log('WARNING: Primary Admin account does not have an email address!  Unable to send administrative Email.', false, 'SYSTEM');
4209				}
4210			} else {
4211				cacti_log('WARNING: Primary Admin account set to an invalid user!  Unable to send administrative Email.', false, 'SYSTEM');
4212			}
4213		} else {
4214			cacti_log('WARNING: Primary Admin account notifications disabled!  Unable to send administrative Email.', false, 'SYSTEM');
4215		}
4216	} else {
4217		cacti_log('WARNING: Primary Admin account not set!  Unable to send administrative Email.', false, 'SYSTEM');
4218	}
4219}
4220
4221function send_mail($to, $from, $subject, $body, $attachments = '', $headers = '', $html = false) {
4222	$fromname = '';
4223	if (is_array($from)) {
4224		$fromname = $from[1];
4225		$from     = $from[0];
4226	}
4227
4228	if ($from == '') {
4229		$from     = read_config_option('settings_from_email');
4230		$fromname = read_config_option('settings_from_name');
4231	} elseif ($fromname == '') {
4232		$full_name = db_fetch_cell_prepared('SELECT full_name
4233			FROM user_auth
4234			WHERE email_address = ?',
4235			array($from));
4236
4237		if (empty($full_name)) {
4238			$fromname = $from;
4239		} else {
4240			$fromname = $full_name;
4241		}
4242	}
4243
4244	$from = array(0 => $from, 1 => $fromname);
4245	return mailer($from, $to, '', '', '', $subject, $body, '', $attachments, $headers, $html);
4246}
4247
4248/**
4249 * mailer - function to send mails to users
4250 *
4251 * @param $from        - single contact (see below)
4252 * @param $to          - single or multiple contacts (see below)
4253 * @param $cc          - none, single or multiple contacts (see below)
4254 * @param $bcc         - none, single or multiple contacts (see below)
4255 * @param $replyto     - none, single or multiple contacts (see below)
4256 *                       note that this value is used when hitting reply (overriding the default of using from)
4257 * @param $subject     - the email subject
4258 * @param $body        - the email body, in HTML format.  If content_text is not set, the function will attempt to extract
4259 *                       from the HTML format.
4260 * @param $body_text   - the email body in TEXT format.  If set, it will override the stripping tags method
4261 * @param $attachments - the emails attachments as an array
4262 * @param $headers     - an array of name value pairs representing custom headers.
4263 * @param $html        - if set to true, html is the default, otherwise text format will be used
4264 *
4265 * For contact parameters, they can accept arrays containing zero or more values in the forms of:
4266 *     'email@email.com,email2@email.com,email3@email.com'
4267 *     array('email1@email.com' => 'My email', 'email2@email.com' => 'Your email', 'email3@email.com' => 'Whose email')
4268 *     array(array('email' => 'email1@email.com', 'name' => 'My email'), array('email' => 'email2@email.com',
4269 *         'name' => 'Your email'), array('email' => 'email3@email.com', 'name' => 'Whose email'))
4270 *
4271 * The $from field will only use the first contact specified.  If no contact is provided for $replyto
4272 * then $from is used for that too. If $from is empty, it will default to cacti@<server> or if no server name can
4273 * be found, it will use cacti@cacti.net
4274 *
4275 * The $attachments parameter may either be a single string, or a list of attachments
4276 * either as strings or an array.  The array can have the following keys:
4277 *
4278 * filename    : name of the file to attach (display name for graphs)
4279 * display     : displayed name of the attachment
4280 * mime_type   : MIME type to be set against the attachment.  If blank or missing mailer will attempt to auto detect
4281 * attachment  : String containing attachment for image-based attachments (<GRAPH> or <GRAPH:#> activates graph mode
4282 *               and requires $body parameter is HTML containing one of those values)
4283 * inline      : Whether to attach 'inline' (default for graph mode) or as 'attachment' (default for all others)
4284 * encoding    : Encoding type, normally base64
4285 */
4286function mailer($from, $to, $cc, $bcc, $replyto, $subject, $body, $body_text = '', $attachments = '', $headers = '', $html = true) {
4287	global $config, $cacti_locale, $mail_methods;
4288
4289	require_once($config['include_path'] . '/vendor/phpmailer/src/Exception.php');
4290	require_once($config['include_path'] . '/vendor/phpmailer/src/PHPMailer.php');
4291	require_once($config['include_path'] . '/vendor/phpmailer/src/SMTP.php');
4292
4293	$start_time = microtime(true);
4294
4295	// Create the PHPMailer instance
4296	$mail = new PHPMailer\PHPMailer\PHPMailer;
4297
4298	// Set a reasonable timeout of 5 seconds
4299	$timeout = read_config_option('settings_smtp_timeout');
4300	if (empty($timeout) || $timeout < 0 || $timeout > 300) {
4301		$mail->Timeout = 5;
4302	} else {
4303		$mail->Timeout = $timeout;
4304	}
4305
4306	$langparts = explode('-', $cacti_locale);
4307	if (file_exists($config['include_path'] . '/vendor/phpmailer/language/phpmailer.lang-' . $langparts[0] . '.php')) {
4308		$mail->setLanguage($langparts[0], $config['include_path'] . '/vendor/phpmailer/language/');
4309	}
4310
4311	$how = read_config_option('settings_how');
4312	if ($how < 0 || $how > 2) {
4313		$how = 0;
4314	}
4315
4316	if ($how == 0) {
4317		$mail->isMail();
4318	} elseif ($how == 1) {
4319		$mail->Sendmail = read_config_option('settings_sendmail_path');
4320		$mail->isSendmail();
4321	} elseif ($how == 2) {
4322		$mail->isSMTP();
4323		$mail->Host     = read_config_option('settings_smtp_host');
4324		$mail->Port     = read_config_option('settings_smtp_port');
4325
4326		if (read_config_option('settings_smtp_username') != '') {
4327			$mail->SMTPAuth = true;
4328			$mail->Username = read_config_option('settings_smtp_username');
4329
4330			if (read_config_option('settings_smtp_password') != '') {
4331				$mail->Password = read_config_option('settings_smtp_password');
4332			}
4333		} else {
4334			$mail->SMTPAuth = false;
4335		}
4336
4337		$secure  = read_config_option('settings_smtp_secure');
4338		if (!empty($secure) && $secure != 'none') {
4339			$mail->SMTPSecure = true;
4340			if (substr_count($mail->Host, ':') == 0) {
4341				$mail->Host = $secure . '://' . $mail->Host;
4342			}
4343		} else {
4344			$mail->SMTPAutoTLS = false;
4345			$mail->SMTPSecure = false;
4346		}
4347	}
4348
4349	/* perform data substitution */
4350	if (strpos($subject, '|date_time|') !== false) {
4351		$date = read_config_option('date');
4352		if (!empty($date)) {
4353			$time = strtotime($date);
4354		} else {
4355			$time = time();
4356		}
4357
4358		$subject = str_replace('|date_time|', date(CACTI_DATE_TIME_FORMAT, $time), $subject);
4359	}
4360
4361	/*
4362	 * Set the from details using the variable passed in
4363	 * - if name is blank, use setting's name
4364	 * - if email is blank, use setting's email, otherwise default to
4365	 *   cacti@<server> or cacti@cacti.net if no known server name
4366	 */
4367	$from = parse_email_details($from, 1);
4368
4369	// from name was empty, use value in settings
4370	if (empty($from['name'])) {
4371		$from['name'] = read_config_option('settings_from_name');
4372	}
4373
4374	// from email was empty, use email in settings
4375	if (empty($from['email'])) {
4376		$from['email'] = read_config_option('settings_from_email');
4377	}
4378
4379	if (empty($from['email'])) {
4380		if (isset($_SERVER['HOSTNAME'])) {
4381			$from['email'] = 'Cacti@' . $_SERVER['HOSTNAME'];
4382		} else {
4383			$from['email'] = 'Cacti@cacti.net';
4384		}
4385
4386		if (empty($from['name'])) {
4387			$from['name'] = 'Cacti';
4388		}
4389	}
4390
4391	// Sanity test the from email
4392	if (!filter_var($from['email'], FILTER_VALIDATE_EMAIL)) {
4393		return 'Bad email address format. Invalid from email address ' . $from['email'];
4394	}
4395
4396	$fromText  = add_email_details(array($from), $result, array($mail, 'setFrom'));
4397
4398	if ($result == false) {
4399		return record_mailer_error($fromText, $mail->ErrorInfo);
4400	}
4401
4402	// Convert $to variable to proper array structure
4403	$to        = parse_email_details($to);
4404	$toText    = add_email_details($to, $result, array($mail, 'addAddress'));
4405
4406	if ($result == false) {
4407		return record_mailer_error($toText, $mail->ErrorInfo);
4408	}
4409
4410	$cc        = parse_email_details($cc);
4411	$ccText    = add_email_details($cc, $result, array($mail, 'addCC'));
4412
4413	if ($result == false) {
4414		return record_mailer_error($ccText, $mail->ErrorInfo);
4415	}
4416
4417	$bcc       = parse_email_details($bcc);
4418	$bccText   = add_email_details($bcc, $result, array($mail, 'addBCC'));
4419
4420	if ($result == false) {
4421		return record_mailer_error($bccText, $mail->ErrorInfo);
4422	}
4423
4424	// This is a failsafe, should never happen now
4425	if (!(cacti_sizeof($to) || cacti_sizeof($cc) || cacti_sizeof($bcc))) {
4426		cacti_log('ERROR: No recipient address set!!', false, 'MAILER');
4427		cacti_debug_backtrace('MAILER ERROR');
4428
4429		return __('Mailer Error: No recipient address set!!<br>If using the <i>Test Mail</i> link, please set the <b>Alert e-mail</b> setting.');
4430	}
4431
4432	$replyto   = parse_email_details($replyto);
4433	$replyText = add_email_details($replyto, $result, array($mail, 'addReplyTo'));
4434
4435	if ($result == false) {
4436		return record_mailer_error($replyText, $mail->ErrorInfo);
4437	}
4438
4439	$body = str_replace('<SUBJECT>', $subject,   $body);
4440	$body = str_replace('<TO>',      $toText,    $body);
4441	$body = str_replace('<CC>',      $ccText,    $body);
4442	$body = str_replace('<FROM>',    $fromText,  $body);
4443	$body = str_replace('<REPLYTO>', $replyText, $body);
4444
4445	$body_text = str_replace('<SUBJECT>', $subject,   $body_text);
4446	$body_text = str_replace('<TO>',      $toText,    $body_text);
4447	$body_text = str_replace('<CC>',      $ccText,    $body_text);
4448	$body_text = str_replace('<FROM>',    $fromText,  $body_text);
4449	$body_text = str_replace('<REPLYTO>', $replyText, $body_text);
4450
4451	// Set the subject
4452	$mail->Subject = $subject;
4453
4454	// Support i18n
4455	$mail->CharSet = 'UTF-8';
4456	$mail->Encoding = 'base64';
4457
4458	// Set the wordwrap limits
4459	$wordwrap = read_config_option('settings_wordwrap');
4460	if ($wordwrap == '') {
4461		$wordwrap = 76;
4462	} elseif ($wordwrap > 9999) {
4463		$wordwrap = 9999;
4464	} elseif ($wordwrap < 0) {
4465		$wordwrap = 76;
4466	}
4467
4468	$mail->WordWrap = $wordwrap;
4469	$mail->setWordWrap();
4470
4471	if (!$html) {
4472		$mail->ContentType = 'text/plain';
4473	} else {
4474		$mail->ContentType = 'text/html';
4475	}
4476
4477	$i = 0;
4478
4479	// Handle Graph Attachments
4480	if (!empty($attachments) && !is_array($attachments)) {
4481		$attachments = array('attachment' => $attachments);
4482	}
4483
4484	if (is_array($attachments) && cacti_sizeof($attachments)) {
4485		$graph_mode = (substr_count($body, '<GRAPH>') > 0);
4486		$graph_ids = (substr_count($body, '<GRAPH:') > 0);
4487
4488		$default_opts = array(
4489			// MIME type to be set against the attachment
4490			'mime_type'  => '',
4491			// Display name of the attachment
4492			'filename'    => '',
4493			// String containing attachment for image-based attachments
4494			'attachment' => '',
4495			// Whether to attach inline or as attachment
4496			'inline'     => ($graph_mode || $graph_ids) ? 'inline' : 'attachment',
4497			// Encoding type, normally base64
4498			'encoding'   => 'base64',
4499		);
4500
4501		foreach($attachments as $attachment) {
4502			if (!is_array($attachment)) {
4503				$attachment = array('attachment' => $attachment);
4504			}
4505
4506			foreach ($default_opts as $opt_name => $opt_default) {
4507				if (!array_key_exists($opt_name, $attachment)) {
4508					$attachment[$opt_name] = $opt_default;
4509				}
4510			}
4511
4512			if (!empty($attachment['attachment'])) {
4513				/* get content id and create attachment */
4514				$cid = getmypid() . '_' . $i . '@' . 'localhost';
4515
4516				if (empty($attachment['filename']) && file_exists($attachment['attachment'])) {
4517					$attachment['filename'] = $attachment['attachment'];
4518				}
4519
4520				/* attempt to attach */
4521				if (!($graph_mode || $graph_ids)) {
4522					if (!empty($attachment['attachment']) && @file_exists($attachment['attachment'])) {
4523						$result = $mail->addAttachment($attachment['attachment'], $attachment['filename'], $attachment['encoding'], $attachment['mime_type'], $attachment['inline']);
4524					} else {
4525						$result = $mail->addStringAttachment($attachment['attachment'], $attachment['filename'], 'base64', $attachment['mime_type'], $attachment['inline']);
4526					}
4527				} else {
4528					if (!empty($attachment['attachment']) && @file_exists($attachment['attachment'])) {
4529						$result = $mail->addEmbeddedImage($attachment['attachment'], $cid, $attachment['filename'], $attachment['encoding'], $attachment['mime_type'], $attachment['inline']);
4530					} else {
4531						$result = $mail->addStringEmbeddedImage($attachment['attachment'], $cid, $attachment['filename'], 'base64', $attachment['mime_type'], $attachment['inline']);
4532					}
4533				}
4534
4535				if ($result == false) {
4536					cacti_log('ERROR: ' . $mail->ErrorInfo, false, 'MAILER');
4537					return $mail->ErrorInfo;
4538				}
4539
4540				$i++;
4541				if ($graph_mode) {
4542					$body = str_replace('<GRAPH>', "<br><br><img src='cid:$cid'>", $body);
4543				} elseif ($graph_ids) {
4544					/* handle the body text */
4545					switch ($attachment['inline']) {
4546						case 'inline':
4547							$body = str_replace('<GRAPH:' . $attachment['local_graph_id'] . ':' . $attachment['timespan'] . '>', "<img src='cid:$cid' >", $body);
4548							break;
4549						case 'attachment':
4550							$body = str_replace('<GRAPH:' . $attachment['local_graph_id'] . ':' . $attachment['timespan'] . '>', '', $body);
4551							break;
4552					}
4553				}
4554			}
4555		}
4556	}
4557
4558	/* process custom headers */
4559	if (is_array($headers) && cacti_sizeof($headers)) {
4560		foreach($headers as $name => $value) {
4561			$mail->addCustomHeader($name, $value);
4562		}
4563	}
4564
4565	// Set both html and non-html bodies
4566	$brs = array('<br>', '<br />', '</br>');
4567	if ($html) {
4568		$body  = $body . '<br>';
4569	}
4570
4571	if ($body_text == '') {
4572		$body_text = strip_tags(str_ireplace($brs, "\n", $body));
4573	}
4574
4575	$mail->isHTML($html);
4576	$mail->Body = ($html ? $body : $body_text);
4577	if ($html && $body_text != '') {
4578		$mail->AltBody = $body_text;
4579	}
4580
4581	$result   = $mail->send();
4582	$error    = $mail->ErrorInfo; //$result ? '' : $mail->ErrorInfo;
4583	$method   = $mail_methods[intval(read_config_option('settings_how'))];
4584	$rtype    = $result ? 'INFO' : 'WARNING';
4585	$rmsg     = $result ? 'successfully sent' : 'failed';
4586	$end_time = microtime(true);
4587
4588	if ($error != '') {
4589		$message = sprintf("%s: Mail %s via %s from '%s', to '%s', cc '%s', and took %2.2f seconds, Subject '%s'%s",
4590			$rtype, $rmsg, $method, $fromText, $toText, $ccText, ($end_time - $start_time), $subject,
4591			", Error: $error");
4592	} else {
4593		$message = sprintf("%s: Mail %s via %s from '%s', to '%s', cc '%s', and took %2.2f seconds, Subject '%s'",
4594			$rtype, $rmsg, $method, $fromText, $toText, $ccText, ($end_time - $start_time), $subject);
4595	}
4596
4597	cacti_log($message, false, 'MAILER');
4598	if ($result == false) {
4599		cacti_log(cacti_debug_backtrace($rtype), false, 'MAILER');
4600	}
4601
4602	return $error;
4603}
4604
4605function record_mailer_error($retError, $mailError) {
4606	$errorInfo = empty($retError) ? $mailError : $retError;
4607	cacti_log('ERROR: ' . $errorInfo, false, 'CMDPHP MAILER');
4608	cacti_debug_backtrace('MAILER ERROR');
4609	return $errorInfo;
4610}
4611
4612function add_email_details($emails, &$result, callable $addFunc) {
4613	$arrText = array();
4614
4615	foreach ($emails as $e) {
4616		if (!empty($e['email'])) {
4617			//if (is_callable($addFunc)) {
4618			if (!empty($addFunc)) {
4619				$result = $addFunc($e['email'], $e['name']);
4620				if (!$result) {
4621					return '';
4622				}
4623			}
4624
4625			$arrText[] = create_emailtext($e);
4626		} elseif (!empty($e['name'])) {
4627			$result = false;
4628			return 'Bad email format, name but no address: ' . $e['name'];
4629		}
4630	}
4631
4632	$text = implode(',', $arrText);
4633	//print "add_email_sw_details(): $text\n";
4634	return $text;
4635}
4636
4637function parse_email_details($emails, $max_records = 0, $details = array()) {
4638	if (!is_array($emails)) {
4639		$emails = array($emails);
4640	}
4641
4642	$update = array();
4643	foreach ($emails as $check_email) {
4644		if (!empty($check_email)) {
4645			if (!is_array($check_email)) {
4646				$emails = explode(',', $check_email);
4647
4648				foreach($emails as $email) {
4649					$email_array = split_emaildetail($email);
4650					$details[] = $email_array;
4651				}
4652			} else {
4653				$has_name  = array_key_exists('name', $check_email);
4654				$has_email = array_key_exists('email', $check_email);
4655
4656				if ($has_name || $has_email) {
4657					$name  = $has_name  ? $check_email['name']  : '';
4658					$email = $has_email ? $check_email['email'] : '';
4659				} else {
4660					$name  = array_key_exists(1, $check_email) ? $check_email[1] : '';
4661					$email = array_key_exists(0, $check_email) ? $check_email[0] : '';
4662				}
4663
4664				$details[] = array('name' => trim($name), 'email' => trim($email));
4665			}
4666		}
4667	}
4668
4669	if ($max_records == 1) {
4670		$results = count($details) ? $details[0] : array();
4671	} elseif ($max_records != 0 && $max_records < count($details)) {
4672		$results = array();
4673		foreach ($details as $d) {
4674			$results[] = $d;
4675			$max_records--;
4676			if ($max_records == 0) {
4677				break;
4678			}
4679		}
4680	} else {
4681		$results = $details;
4682	}
4683
4684	return $results;
4685}
4686
4687function split_emaildetail($email) {
4688	$rname  = '';
4689	$rmail = '';
4690
4691	if (!is_array($email)) {
4692		$email = trim($email);
4693
4694		$sPattern = '/(?:"?([^"]*)"?\s)?(?:<?(.+@[^>]+)>?)/i';
4695		preg_match($sPattern, $email, $aMatch);
4696
4697		if (isset($aMatch[1])) {
4698			$rname = trim($aMatch[1]);
4699		}
4700
4701		if (isset($aMatch[2])) {
4702			$rmail = trim($aMatch[2]);
4703		}
4704	} else {
4705		$rmail = $email[0];
4706		$rname = $email[1];
4707	}
4708
4709	return array('name' => $rname, 'email' => $rmail);
4710}
4711
4712function create_emailtext($e) {
4713	if (empty($e['email'])) {
4714		$text = '';
4715	} else {
4716		if (empty($e['name'])) {
4717			$text = $e['email'];
4718		} else {
4719			$text = $e['name'] . ' <' . $e['email'] . '>';
4720		}
4721	}
4722
4723	return $text;
4724}
4725
4726function ping_mail_server($host, $port, $user, $password, $timeout = 10, $secure = 'none') {
4727	global $config;
4728
4729    require_once($config['include_path'] . '/vendor/phpmailer/src/Exception.php');
4730    require_once($config['include_path'] . '/vendor/phpmailer/src/PHPMailer.php');
4731    require_once($config['include_path'] . '/vendor/phpmailer/src/SMTP.php');
4732
4733	//Create a new SMTP instance
4734	$smtp = new PHPMailer\PHPMailer\SMTP;
4735
4736	if (!empty($secure) && $secure != 'none') {
4737		$smtp->SMTPSecure = $secure;
4738		if (substr_count($host, ':') == 0) {
4739			$host = $secure . '://' . $host;
4740		}
4741	} else {
4742		$smtp->SMTPAutoTLS = false;
4743		$smtp->SMTPSecure = false;
4744	}
4745
4746	//Enable connection-level debug output
4747	$smtp->do_debug = 0;
4748	//$smtp->do_debug = SMTP::DEBUG_LOWLEVEL;
4749
4750	$results = true;
4751	try {
4752		//Connect to an SMTP server
4753		if ($smtp->connect($host, $port, $timeout)) {
4754			//Say hello
4755			if ($smtp->hello(gethostbyname(gethostname()))) { //Put your host name in here
4756				//Authenticate
4757				if ($user != '') {
4758					if ($smtp->authenticate($user, $password)) {
4759						$results = true;
4760					} else {
4761						throw new Exception(__('Authentication failed: %s', $smtp->getLastReply()));
4762					}
4763				}
4764			} else {
4765				throw new Exception(__('HELO failed: %s', $smtp->getLastReply()));
4766			}
4767		} else {
4768			throw new Exception(__('Connect failed: %s', $smtp->getLastReply()));
4769		}
4770	} catch (Exception $e) {
4771		$results = __('SMTP error: ') . $e->getMessage();
4772		cacti_log($results);
4773	}
4774
4775	//Whatever happened, close the connection.
4776	$smtp->quit(true);
4777
4778	return $results;
4779}
4780
4781function email_test() {
4782	global $config;
4783
4784	$message =  __('This is a test message generated from Cacti.  This message was sent to test the configuration of your Mail Settings.') . '<br><br>';
4785	$message .= __('Your email settings are currently set as follows') . '<br><br>';
4786	$message .= '<b>' . __('Method') . '</b>: ';
4787
4788	print __('Checking Configuration...<br>');
4789
4790	$ping_results = true;
4791	$how = read_config_option('settings_how');
4792	if ($how < 0 || $how > 2)
4793		$how = 0;
4794	if ($how == 0) {
4795		$mail = __('PHP\'s Mailer Class');
4796	} elseif ($how == 1) {
4797		$mail = __('Sendmail') . '<br><b>' . __('Sendmail Path'). '</b>: ';
4798		$sendmail = read_config_option('settings_sendmail_path');
4799		$mail .= $sendmail;
4800	} elseif ($how == 2) {
4801		print __('Method: SMTP') . '<br>';
4802		$mail = __('SMTP') . '<br>';
4803		$smtp_host = read_config_option('settings_smtp_host');
4804		$smtp_port = read_config_option('settings_smtp_port');
4805		$smtp_username = read_config_option('settings_smtp_username');
4806		$smtp_password = read_config_option('settings_smtp_password');
4807		$smtp_secure   = read_config_option('settings_smtp_secure');
4808		$smtp_timeout  = read_config_option('settings_smtp_timeout');
4809
4810		$mail .= "<b>" . __('Device') . "</b>: $smtp_host<br>";
4811		$mail .= "<b>" . __('Port') . "</b>: $smtp_port<br>";
4812
4813		if ($smtp_username != '' && $smtp_password != '') {
4814			$mail .= '<b>' . __('Authentication') . '</b>: true<br>';
4815			$mail .= '<b>' . __('Username') . "</b>: $smtp_username<br>";
4816			$mail .= '<b>' . __('Password') . '</b>: (' . __('Not Shown for Security Reasons') . ')<br>';
4817			$mail .= '<b>' . __('Security') . "</b>: $smtp_secure<br>";
4818		} else {
4819			$mail .= '<b>' . __('Authentication') . '</b>: false<br>';
4820		}
4821
4822		if (read_config_option('settings_ping_mail') == 0) {
4823			$ping_results = ping_mail_server($smtp_host, $smtp_port, $smtp_username, $smtp_password, $smtp_timeout, $smtp_secure);
4824
4825			print __('Ping Results:') . ' ' . ($ping_results == 1 ? __('Success'):$ping_results) . '<br>';
4826
4827			if ($ping_results != 1) {
4828				$mail .= '<b>' . __('Ping Results') . '</b>: ' . $ping_results . '<br>';
4829			} else {
4830				$mail .= '<b>' . __('Ping Results') . '</b>: ' . __('Success') . '<br>';
4831			}
4832		} else {
4833			$ping_results = 1;
4834			$mail .= '<b>' . __('Ping Results') . '</b>: ' . __('Bypassed') . '<br>';
4835		}
4836	}
4837	$message .= $mail;
4838	$message .= '<br>';
4839
4840	$errors = '';
4841	if ($ping_results == 1) {
4842		print __('Creating Message Text...') . '<br><br>';
4843		print "<center><table><tr><td>";
4844		print "<table style='width:100%;'><tr><td>$message</td><tr></table></table></center><br>";
4845		print __('Sending Message...') . '<br><br>';
4846
4847		$global_alert_address = read_config_option('settings_test_email');
4848
4849		$errors = send_mail($global_alert_address, '', __('Cacti Test Message'), $message, '', '', true);
4850		if ($errors == '') {
4851			$errors = __('Success!');
4852		}
4853	} else {
4854		print __('Message Not Sent due to ping failure.'). '<br><br>';
4855	}
4856
4857	print "<center><table><tr><td>";
4858	print "<table><tr><td>$errors</td><tr></table></table></center>";
4859}
4860
4861/**
4862 * gethostbyaddr_wtimeout - This function provides a good method of performing
4863 * a rapid lookup of a DNS entry for a host so long as you don't have to look far.
4864 */
4865function get_dns_from_ip ($ip, $dns, $timeout = 1000) {
4866	/* random transaction number (for routers etc to get the reply back) */
4867	$data = rand(10, 99);
4868
4869	/* trim it to 2 bytes */
4870	$data = substr($data, 0, 2);
4871
4872	/* create request header */
4873	$data .= "\1\0\0\1\0\0\0\0\0\0";
4874
4875	/* split IP into octets */
4876	$octets = explode('.', $ip);
4877
4878	/* perform a quick error check */
4879	if (cacti_count($octets) != 4) return 'ERROR';
4880
4881	/* needs a byte to indicate the length of each segment of the request */
4882	for ($x=3; $x>=0; $x--) {
4883		switch (strlen($octets[$x])) {
4884		case 1: // 1 byte long segment
4885			$data .= "\1"; break;
4886		case 2: // 2 byte long segment
4887			$data .= "\2"; break;
4888		case 3: // 3 byte long segment
4889			$data .= "\3"; break;
4890		default: // segment is too big, invalid IP
4891			return 'ERROR';
4892		}
4893
4894		/* and the segment itself */
4895		$data .= $octets[$x];
4896	}
4897
4898	/* and the final bit of the request */
4899	$data .= "\7in-addr\4arpa\0\0\x0C\0\1";
4900
4901	/* create UDP socket */
4902	$handle = @fsockopen("udp://$dns", 53);
4903
4904	@stream_set_timeout($handle, floor($timeout/1000), ($timeout*1000)%1000000);
4905	@stream_set_blocking($handle, 1);
4906
4907	/* send our request (and store request size so we can cheat later) */
4908	$requestsize = @fwrite($handle, $data);
4909
4910	/* get the response */
4911	$response = @fread($handle, 1000);
4912
4913	/* check to see if it timed out */
4914	$info = @stream_get_meta_data($handle);
4915
4916	/* close the socket */
4917	@fclose($handle);
4918
4919	if ($info['timed_out']) {
4920		return 'timed_out';
4921	}
4922
4923	/* more error handling */
4924	if ($response == '') { return $ip; }
4925
4926	/* parse the response and find the response type */
4927	$type = @unpack('s', substr($response, $requestsize+2));
4928
4929	if (isset($type[1]) && $type[1] == 0x0C00) {
4930		/* set up our variables */
4931		$host = '';
4932		$len = 0;
4933
4934		/* set our pointer at the beginning of the hostname uses the request
4935		   size from earlier rather than work it out.
4936		*/
4937		$position = $requestsize + 12;
4938
4939		/* reconstruct the hostname */
4940		do {
4941			/* get segment size */
4942			$len = unpack('c', substr($response, $position));
4943
4944			/* null terminated string, so length 0 = finished */
4945			if ($len[1] == 0) {
4946				/* return the hostname, without the trailing '.' */
4947				return strtoupper(substr($host, 0, strlen($host) -1));
4948			}
4949
4950			/* add the next segment to our host */
4951			$host .= substr($response, $position+1, $len[1]) . '.';
4952
4953			/* move pointer on to the next segment */
4954			$position += $len[1] + 1;
4955		} while ($len != 0);
4956
4957		/* error - return the hostname we constructed (without the . on the end) */
4958		return strtoupper($ip);
4959	}
4960
4961	/* error - return the hostname */
4962	return strtoupper($ip);
4963}
4964
4965function poller_maintenance () {
4966	global $config;
4967
4968	$command_string = cacti_escapeshellcmd(read_config_option('path_php_binary'));
4969
4970	// If its not set, just assume its in the path
4971	if (trim($command_string) == '') {
4972		$command_string = 'php';
4973	}
4974
4975	$extra_args = ' -q ' . cacti_escapeshellarg($config['base_path'] . '/poller_maintenance.php');
4976
4977	exec_background($command_string, $extra_args);
4978}
4979
4980function clog_admin() {
4981	if (!isset($_SESSION['sess_clog_level'])) {
4982		clog_authorized();
4983	}
4984
4985	if ($_SESSION["sess_clog_level"] == CLOG_PERM_ADMIN) {
4986		return true;
4987	} else {
4988		return false;
4989	}
4990}
4991
4992function clog_authorized() {
4993	if (!isset($_SESSION['sess_clog_level'])) {
4994		if (isset($_SESSION['sess_user_id'])) {
4995			if (is_realm_allowed(18)) {
4996				$_SESSION['sess_clog_level'] = CLOG_PERM_ADMIN;
4997			} else {
4998				if (is_realm_allowed(19)) {
4999					$_SESSION['sess_clog_level'] = CLOG_PERM_USER;
5000				} else {
5001					$_SESSION['sess_clog_level'] = CLOG_PERM_NONE;
5002				}
5003			}
5004		} else {
5005			$_SESSION['sess_clog_level'] = CLOG_PERM_NONE;
5006		}
5007	}
5008
5009	if ($_SESSION['sess_clog_level'] == CLOG_PERM_USER) {
5010		return true;
5011	} elseif ($_SESSION['sess_clog_level'] == CLOG_PERM_ADMIN) {
5012		return true;
5013	} else {
5014		return false;
5015	}
5016}
5017
5018function cacti_debug_backtrace($entry = '', $html = false, $record = true, $limit = 0, $skip = 0) {
5019	global $config;
5020
5021	$skip = $skip >= 0 ? $skip : 1;
5022	$limit = $limit > 0 ? ($limit + $skip) : 0;
5023
5024	$callers = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit);
5025	while ($skip > 0) {
5026		array_shift($callers);
5027		$skip--;
5028	}
5029
5030	$s='';
5031	foreach ($callers as $c) {
5032		if (isset($c['line'])) {
5033			$line = '[' . $c['line'] . ']';
5034		} else {
5035			$line = '';
5036		}
5037
5038		if (isset($c['file'])) {
5039			$file = str_replace($config['base_path'], '', $c['file']) . $line;
5040		} else {
5041			$file = $line;
5042		}
5043
5044		$func = $c['function'].'()';
5045		if (isset($c['class'])) {
5046			$func = $c['class'] . $c['type'] . $func;
5047		}
5048
5049		$s = ($file != '' ? $file . ':':'') . "$func" . (empty($s) ? '' : ', ') . $s;
5050	}
5051
5052	if (!empty($s)) {
5053		$s = ' (' . $s . ')';
5054	}
5055
5056	if ($record) {
5057		if ($html) {
5058			print "<table style='width:100%;text-align:center;'><tr><td>$s</td></tr></table>\n";
5059		}
5060
5061		cacti_log(trim("$entry Backtrace: " . clean_up_lines($s)), false);
5062	} else {
5063		if (!empty($entry)) {
5064			return trim("$entry Backtrace: " . clean_up_lines($s));
5065		} else {
5066			return trim(clean_up_lines($s));
5067		}
5068	}
5069}
5070
5071/**
5072 * calculate_percentiles - Given and array of numbers, calculate the Nth percentile,
5073 * optionally, return an array of numbers containing elements required for
5074 * a whisker chart.
5075 *
5076 * @param $data       - an array of data
5077 * @param $percentile - the Nth percentile to calculate.  By default 95th.
5078 * @param $whisker    - if whisker is true, an array of values will be returned
5079 *                      including 25th, median, 75th, and 90th percentiles.
5080 *
5081 * @return - either the Nth percentile, the elements for a whisker chart,
5082 *            or false if there is insufficient data to determine.
5083 */
5084function calculate_percentiles($data, $percentile = 95, $whisker = false) {
5085	if ($percentile > 0 && $percentile < 1) {
5086		$p = $percentile;
5087	} elseif ($percentile > 1 && $percentile <= 100) {
5088		$p = $percentile * .01;
5089	} else {
5090		return false;
5091	}
5092
5093	if ($whisker) {
5094		$tiles = array(
5095			'25th' => 0.25,
5096			'50th' => 0.50,
5097			'75th' => 0.75,
5098			'90th' => 0.90,
5099			'95th' => 0.95,
5100		);
5101	} else {
5102		$tiles = array(
5103			'custom' => $p
5104		);
5105	}
5106
5107	$results  = array();
5108	$elements = cacti_sizeof($data);
5109
5110	/* sort the array to return */
5111	sort($data);
5112
5113	foreach($tiles as $index => $p) {
5114		/* calculate offsets into the array */
5115		$allindex    = ($elements - 1) * $p;
5116		$intvalindex = intval($allindex);
5117		$floatval    = $allindex - $intvalindex;
5118
5119		if (!is_float($floatval)) {
5120			$ptile = $data[$intvalindex];
5121		} else {
5122			if ($elements > $intvalindex + 1) {
5123				$ptile = $floatval * ($data[$intvalindex + 1] - $data[$intvalindex]) + $data[$intvalindex];
5124			} else {
5125				$ptile = $data[$intvalindex];
5126			}
5127		}
5128
5129		if ($index == 'custom') {
5130			return $ptile;
5131		} else {
5132			$results[$index] = $ptile;
5133		}
5134	}
5135
5136	return $results;
5137}
5138
5139function get_timeinstate($host) {
5140	$interval = read_config_option('poller_interval');
5141	if ($host['availability_method'] == 0) {
5142		$time = 0;
5143	} elseif (isset($host['instate'])) {
5144		$time = $host['instate'];
5145	} elseif ($host['status_event_count'] > 0 && ($host['status'] == 1 || $host['status'] == 2 || $host['status'] == 5)) {
5146		$time = $host['status_event_count'] * $interval;
5147	} elseif (strtotime($host['status_rec_date']) < 943916400 && ($host['status'] == 0 || $host['status'] == 3)) {
5148		$time = $host['total_polls'] * $interval;
5149	} elseif (strtotime($host['status_rec_date']) > 943916400) {
5150		$time = time() - strtotime($host['status_rec_date']);
5151	} elseif ($host['snmp_sysUpTimeInstance'] > 0) {
5152		$time = $host['snmp_sysUpTimeInstance']/100;
5153	} else {
5154		$time = 0;
5155	}
5156
5157	if ($time > 2E13) {
5158		$time = 0;
5159	}
5160
5161	return ($time > 0) ? get_daysfromtime($time) : __('N/A');
5162}
5163
5164function get_uptime($host) {
5165	return ($host['snmp_sysUpTimeInstance'] > 0) ? get_daysfromtime($host['snmp_sysUpTimeInstance']/100) : __('N/A');
5166}
5167
5168function get_daysfromtime($time, $secs = false, $pad = '', $format = DAYS_FORMAT_SHORT, $all = false) {
5169	global $days_from_time_settings;
5170
5171	// Ensure we use an existing format or we'll end up with no text at all
5172	if (!isset($days_from_time_settings['text'][$format])) {
5173		$format = DAYS_FORMAT_SHORT;
5174	}
5175
5176	$mods = $days_from_time_settings['mods'];
5177	$text = $days_from_time_settings['text'][$format];
5178
5179	$result = '';
5180	foreach ($mods as $index => $mod) {
5181		if ($mod > 0 || $secs) {
5182			if ($time >= $mod) {
5183				if ($mod < 1) {
5184					$mod = 1;
5185				}
5186				$val   = floor($time/$mod);
5187				$time %= $mod;
5188			} else {
5189				$val   = 0;
5190			}
5191
5192			if ($all || $val > 0) {
5193				$result .= padleft($pad, $val, 2) . $text['prefix'] . $text[$index] . $text['suffix'];
5194				$all = true;
5195			}
5196		}
5197	}
5198
5199	return trim($result,$text['suffix']);
5200}
5201
5202function padleft($pad = '', $value = '', $min = 2) {
5203	$result = "$value";
5204	if (strlen($result) < $min && $pad != '') {
5205		$padded = $pad . $result;
5206		while ($padded != $result && strlen($result) < $min) {
5207			$padded = $pad . $result;
5208		}
5209		$result = $padded;
5210	}
5211	return $result;
5212}
5213
5214function get_classic_tabimage($text, $down = false) {
5215	global $config;
5216
5217	$images = array(
5218		false => 'tab_template_blue.gif',
5219		true  => 'tab_template_red.gif'
5220	);
5221
5222	if ($text == '') return false;
5223
5224	$text = strtolower($text);
5225
5226	$possibles = array(
5227		array('DejaVuSans-Bold.ttf', 9, true),
5228		array('DejaVuSansCondensed-Bold.ttf', 9, false),
5229		array('DejaVuSans-Bold.ttf', 9, false),
5230		array('DejaVuSansCondensed-Bold.ttf', 9, false),
5231		array('DejaVuSans-Bold.ttf', 8, false),
5232		array('DejaVuSansCondensed-Bold.ttf', 8, false),
5233		array('DejaVuSans-Bold.ttf', 7, false),
5234		array('DejaVuSansCondensed-Bold.ttf', 7, true),
5235	);
5236
5237	$y        = 30;
5238	$x        = 44;
5239	$wlimit   = 72;
5240	$wrapsize = 12;
5241
5242	if (file_exists($config['base_path'] . '/images/' . $images[$down])) {
5243		$originalpath = getenv('GDFONTPATH');
5244		putenv('GDFONTPATH=' . $config['base_path'] . '/include/fonts/');
5245
5246		$template = imagecreatefromgif ($config['base_path'] . '/images/' . $images[$down]);
5247
5248		$w = imagesx($template);
5249		$h = imagesy($template);
5250
5251		$tab = imagecreatetruecolor($w, $h);
5252		imagecopy($tab, $template, 0, 0, 0, 0, $w, $h);
5253
5254		$txcol = imagecolorat($tab, 0, 0);
5255		imagecolortransparent($tab,$txcol);
5256
5257		$white = imagecolorallocate($tab, 255, 255, 255);
5258		$ttf_functions = function_exists('imagettftext') && function_exists('imagettfbbox');
5259
5260		if ($ttf_functions) {
5261			foreach ($possibles as $variation) {
5262				$font     = $variation[0];
5263				$fontsize = $variation[1];
5264
5265				$lines = array();
5266
5267				// if no wrapping is requested, or no wrapping is possible...
5268				if ((!$variation[2]) || ($variation[2] && strpos($text,' ') === false)) {
5269					$bounds  = imagettfbbox($fontsize, 0, $font, $text);
5270					$w       = $bounds[4] - $bounds[0];
5271					$h       = $bounds[1] - $bounds[5];
5272					$realx   = $x - $w/2 -1;
5273					$lines[] = array($text, $font, $fontsize, $realx, $y);
5274					$maxw    = $w;
5275				} else {
5276					$texts = explode("\n", wordwrap($text, $wrapsize), 2);
5277					$line  = 1;
5278					$maxw  = 0;
5279					foreach ($texts as $txt) {
5280						$bounds  = imagettfbbox($fontsize, 0, $font, $txt);
5281						$w       = $bounds[4] - $bounds[0];
5282						$h       = $bounds[1] - $bounds[5];
5283						$realx   = $x - $w/2 -1;
5284						$realy   = $y - $h * $line + 3;
5285						$lines[] = array($txt, $font, $fontsize, $realx, $realy);
5286						if ($maxw < $w) {
5287							$maxw = $w;
5288						}
5289
5290						$line--;
5291					}
5292				}
5293
5294				if ($maxw<$wlimit) break;
5295			}
5296		} else {
5297			while ($text > '') {
5298				for ($fontid = 5; $fontid>0; $fontid--) {
5299					$fontw = imagefontwidth($fontid);
5300					$fonth = imagefontheight($fontid);
5301					$realx = ($w - ($fontw * strlen($text)))/2;
5302					$realy = ($h - $fonth - 5);
5303
5304					// Since we can't use FreeType, lets use a fixed location
5305					$lines = array();
5306					$lines[] = array($text, $fontid, 0, $realx, $realy);
5307
5308					if ($realx > 10 && $realy > 0) break;
5309				}
5310
5311				if ($fontid == 0) {
5312					$spacer = strrpos($text,' ');
5313					if ($spacer === false) {
5314						$spacer = strlen($text) - 1;
5315					}
5316					$text = substr($text,0,$spacer);
5317				} else {
5318					break;
5319				}
5320			}
5321		}
5322
5323
5324		foreach ($lines as $line) {
5325			if ($ttf_functions) {
5326				imagettftext($tab, $line[2], 0, $line[3], $line[4], $white, $line[1], $line[0]);
5327			} else {
5328				imagestring($tab, $line[1], $line[3], $line[4], $line[0], $white);
5329			}
5330		}
5331
5332		putenv('GDFONTPATH=' . $originalpath);
5333
5334		imagetruecolortopalette($tab, true, 256);
5335
5336		// generate the image an return the data directly
5337		ob_start();
5338		imagegif ($tab);
5339		$image = ob_get_contents();
5340		ob_end_clean();
5341
5342		return("data:image/gif;base64," . base64_encode($image));
5343	} else {
5344		return false;
5345	}
5346}
5347
5348function cacti_oid_numeric_format() {
5349	if (function_exists('snmp_set_oid_output_format')) {
5350		snmp_set_oid_output_format(SNMP_OID_OUTPUT_NUMERIC);
5351	} elseif (function_exists("snmp_set_oid_numeric_print")) {
5352		snmp_set_oid_numeric_print(true);
5353	}
5354}
5355
5356function IgnoreErrorHandler($message) {
5357	global $snmp_error;
5358
5359	$snmp_ignore = array(
5360		'No response from',
5361		'noSuchName',
5362		'No Such Object',
5363		'Error in packet',
5364		'This name does not exist',
5365		'End of MIB',
5366		'Timeout',
5367		'Unknown host',
5368		'Invalid object identifier',
5369		'Name or service not known'
5370	);
5371
5372	foreach ($snmp_ignore as $i) {
5373		if (strpos($message, $i)) {
5374			$snmp_error = trim($message, "\\\n\t ");
5375			return true;
5376		}
5377	}
5378
5379	$ignore = array(
5380		'unable to read from socket'  # ping.php line 387 socket refusal
5381	);
5382
5383	foreach ($ignore as $i) {
5384		if (strpos($message, $i)) {
5385			return true;
5386		}
5387	}
5388
5389	return false;
5390}
5391
5392function CactiErrorHandler($level, $message, $file, $line, $context = array()) {
5393	global $phperrors;
5394
5395	if (defined('IN_CACTI_INSTALL')) {
5396		return true;
5397	}
5398
5399	if (IgnoreErrorHandler($message)) {
5400		return true;
5401	}
5402
5403	if (error_reporting() == 0) {
5404		return true;
5405	}
5406
5407	preg_match("/.*\/plugins\/([\w-]*)\/.*/", $file, $output_array);
5408
5409	$plugin = (isset($output_array[1]) ? $output_array[1] : '');
5410	$error  = 'PHP ' . $phperrors[$level] . ($plugin != '' ? " in  Plugin '$plugin'" : '') . ": $message in file: $file  on line: $line";
5411
5412	switch ($level) {
5413		case E_COMPILE_ERROR:
5414		case E_CORE_ERROR:
5415		case E_ERROR:
5416		case E_PARSE:
5417			cacti_log($error, false, 'ERROR');
5418			cacti_debug_backtrace('PHP ERROR PARSE', false, true, 0, 1);
5419			if ($plugin != '') {
5420				api_plugin_disable_all($plugin);
5421				cacti_log("ERRORS DETECTED - DISABLING PLUGIN '$plugin'");
5422				admin_email(__('Cacti System Warning'), __('Cacti disabled plugin %s due to the following error: %s!  See the Cacti logfile for more details.', $plugin, $error));
5423			}
5424			break;
5425		case E_RECOVERABLE_ERROR:
5426		case E_USER_ERROR:
5427			cacti_log($error, false, 'ERROR');
5428			cacti_debug_backtrace('PHP ERROR', false, true, 0, 1);
5429			break;
5430		case E_COMPILE_WARNING:
5431		case E_CORE_WARNING:
5432		case E_USER_WARNING:
5433		case E_WARNING:
5434			cacti_log($error, false, 'ERROR');
5435			cacti_debug_backtrace('PHP ERROR WARNING', false, true, 0, 1);
5436			break;
5437		case E_NOTICE:
5438		case E_USER_NOTICE:
5439			cacti_log($error, false, 'ERROR');
5440			cacti_debug_backtrace('PHP ERROR NOTICE', false, true, 0, 1);
5441			break;
5442		case E_STRICT:
5443			cacti_log($error, false, 'ERROR');
5444			cacti_debug_backtrace('PHP ERROR STRICT', false, true, 0, 1);
5445			break;
5446		default:
5447			cacti_log($error, false, 'ERROR');
5448			cacti_debug_backtrace('PHP ERROR', false, true, 0, 1);
5449	}
5450
5451	return false;
5452}
5453
5454function CactiShutdownHandler() {
5455	global $phperrors;
5456	$error = error_get_last();
5457
5458	if (is_array($error)) {
5459		if (isset($error['message']) && IgnoreErrorHandler($error['message'])) {
5460			return true;
5461		}
5462
5463		if (isset($error['type'])) {
5464			switch ($error['type']) {
5465				case E_ERROR:
5466				case E_CORE_ERROR:
5467				case E_COMPILE_ERROR:
5468				case E_CORE_WARNING:
5469				case E_COMPILE_WARNING:
5470				case E_PARSE:
5471					preg_match('/.*\/plugins\/([\w-]*)\/.*/', $error['file'], $output_array);
5472
5473					$plugin = (isset($output_array[1]) ? $output_array[1] : '' );
5474
5475					$message = 'PHP ' . $phperrors[$error['type']] .
5476						($plugin != '' ? " in  Plugin '$plugin'" : '') . ': ' . $error['message'] .
5477						' in file: ' .  $error['file'] . ' on line: ' . $error['line'];
5478
5479					cacti_log($message, false, 'ERROR');
5480					cacti_debug_backtrace('PHP ERROR', false, true, 0, 1);
5481
5482					if ($plugin != '') {
5483						api_plugin_disable_all($plugin);
5484						cacti_log("ERRORS DETECTED - DISABLING PLUGIN '$plugin'");
5485						admin_email(__('Cacti System Warning'), __('Cacti disabled plugin %s due to the following error: %s!  See the Cacti logfile for more details.', $plugin, $message));
5486					}
5487			}
5488		}
5489	}
5490}
5491
5492/**
5493 * enable_device_debug - Enables device debug for a device
5494 * if it is disabled.
5495 *
5496 * @param $host_id - the device id to search for
5497 *
5498 * @return - void
5499 */
5500function enable_device_debug($host_id) {
5501	$device_debug = read_config_option('selective_device_debug', true);
5502	if ($device_debug != '') {
5503		$devices = explode(',', $device_debug);
5504		if (array_search($host_id, $devices) === false) {
5505			set_config_option('selective_device_debug', $device_debug . ',' . $host_id);
5506		}
5507	} else {
5508		set_config_option('selective_device_debug', $host_id);
5509	}
5510}
5511
5512/**
5513 * disable_device_debug - Disables device debug for a device
5514 * if it is enabled.
5515 *
5516 * @param $host_id - the device id to search for
5517 *
5518 * @return - void
5519 */
5520function disable_device_debug($host_id) {
5521	$device_debug = read_config_option('selective_device_debug', true);
5522	if ($device_debug != '') {
5523		$devices = explode(',', $device_debug);
5524		foreach($devices as $key => $device) {
5525			if ($device == $host_id) {
5526				unset($devices[$key]);
5527				break;
5528			}
5529		}
5530		set_config_option('selective_device_debug', implode(',', $devices));
5531	}
5532}
5533
5534/**
5535 * is_device_debug_enabled - Determines if device debug is enabled
5536 * for a device.
5537 *
5538 * @param $host_id - the device id to search for
5539 *
5540 * @return - boolean true or false
5541 */
5542function is_device_debug_enabled($host_id) {
5543	$device_debug = read_config_option('selective_device_debug', true);
5544	if ($device_debug != '') {
5545		$devices = explode(',', $device_debug);
5546		if (array_search($host_id, $devices) !== false) {
5547			return true;
5548		}
5549	}
5550
5551	return false;
5552}
5553
5554/**
5555 * get_url_type - Determines if remote communications are over
5556 * http or https for remote services.
5557 *
5558 * @return - http or https
5559 */
5560function get_url_type() {
5561	if (read_config_option('force_https') == 'on') {
5562		return 'https';
5563	} else {
5564		return 'http';
5565	}
5566}
5567
5568/**
5569 * get_default_contextoption - Sets default context options for self-signed SSL
5570 * related protocols if necessary. Allows plugins to add additional header information
5571 * to fulfill system setup related requirements like the usage of Web Single Login
5572 * cookies for example.
5573 *
5574 * @return - an array of stream context options or false
5575 */
5576function get_default_contextoption($timeout = '') {
5577	$fgc_contextoption = false;
5578
5579	if ($timeout == '') {
5580		$timeout = read_config_option('remote_agent_timeout');
5581	}
5582
5583	if (!is_numeric($timeout)) {
5584		$timeout = 5;
5585	}
5586
5587	$protocol = get_url_type();
5588
5589	if (in_array($protocol, array('ssl', 'https', 'ftps'))) {
5590		$fgc_contextoption = array(
5591			'ssl' => array(
5592				'verify_peer' => false,
5593				'verify_peer_name' => false,
5594				'allow_self_signed' => true,
5595			)
5596		);
5597	}
5598
5599	if ($protocol == 'https') {
5600		$fgc_contextoption['https'] = array(
5601			'timeout' => $timeout,
5602			'ignore_errors' => true
5603		);
5604	} elseif ($protocol == 'http') {
5605		$fgc_contextoption['http'] = array(
5606			'timeout' => $timeout,
5607			'ignore_errors' => true
5608		);
5609	}
5610
5611	$fgc_contextoption = api_plugin_hook_function('fgc_contextoption', $fgc_contextoption);
5612
5613	return $fgc_contextoption;
5614}
5615
5616/**
5617 * repair_system_data_input_methods - This utility will repair
5618 * system data input methods when they are detected on the system
5619 *
5620 * @return - null
5621 */
5622function repair_system_data_input_methods($step = 'import') {
5623	$system_hashes = array(
5624		'3eb92bb845b9660a7445cf9740726522', // Get SNMP Data
5625		'bf566c869ac6443b0c75d1c32b5a350e', // Get SNMP Data (Indexed)
5626		'80e9e4c4191a5da189ae26d0e237f015', // Get Script Data (Indexed)
5627		'332111d8b54ac8ce939af87a7eac0c06', // Get Script Server Data (Indexed)
5628	);
5629
5630	$good_field_hashes = array(
5631		'3eb92bb845b9660a7445cf9740726522' => array( // Get SNMP Data (1)
5632			'92f5906c8dc0f964b41f4253df582c38', // IP Address
5633			'012ccb1d3687d3edb29c002ea66e72da', // SNMP Version
5634			'32285d5bf16e56c478f5e83f32cda9ef', // SNMP Community
5635			'fc64b99742ec417cc424dbf8c7692d36', // SNMP Port
5636			'ad14ac90641aed388139f6ba86a2e48b', // SNMP Username
5637			'9c55a74bd571b4f00a96fd4b793278c6', // SNMP Password
5638			'20832ce12f099c8e54140793a091af90', // SNMP Authentication Protocol
5639			'c60c9aac1e1b3555ea0620b8bbfd82cb', // SNMP Privacy Passphrase
5640			'feda162701240101bc74148415ef415a', // SNMP Privacy Protocol
5641			'4276a5ec6e3fe33995129041b1909762'  // SNMP OID
5642		),
5643		'bf566c869ac6443b0c75d1c32b5a350e' => array( // Get SNMP Data (Indexed) (2)
5644			'617cdc8a230615e59f06f361ef6e7728', // IP Address
5645			'b5c23f246559df38662c255f4aa21d6b', // SNMP Version
5646			'acb449d1451e8a2a655c2c99d31142c7', // SNMP Community
5647			'c1f36ee60c3dc98945556d57f26e475b', // SNMP Port
5648			'f4facc5e2ca7ebee621f09bc6d9fc792', // SNMP Username
5649			'1cc1493a6781af2c478fa4de971531cf', // SNMP Password
5650			'2cf7129ad3ff819a7a7ac189bee48ce8', // SNMP Authentication Protocol
5651			'6b13ac0a0194e171d241d4b06f913158', // SNMP Privacy Passphrase
5652			'3a33d4fc65b8329ab2ac46a36da26b72', // SNMP Privacy Protocol
5653			'6027a919c7c7731fbe095b6f53ab127b', // Index Type
5654			'cbbe5c1ddfb264a6e5d509ce1c78c95f', // Index Value
5655			'e6deda7be0f391399c5130e7c4a48b28'  // Output Type ID
5656		),
5657		'80e9e4c4191a5da189ae26d0e237f015' => array( // Get Script Data (Indexed) 11
5658			'd39556ecad6166701bfb0e28c5a11108', // Index Type
5659			'3b7caa46eb809fc238de6ef18b6e10d5', // Index Value
5660			'74af2e42dc12956c4817c2ef5d9983f9', // Output Type ID
5661			'8ae57f09f787656bf4ac541e8bd12537'  // Output Value
5662		),
5663		'332111d8b54ac8ce939af87a7eac0c06' => array( // Get Script Server Data (Indexed) 12
5664			'172b4b0eacee4948c6479f587b62e512', // Index Type
5665			'30fb5d5bcf3d66bb5abe88596f357c26', // Index Value
5666			'31112c85ae4ff821d3b288336288818c', // Output Type ID
5667			'5be8fa85472d89c621790b43510b5043'  // Output Value
5668		)
5669	);
5670
5671	foreach($good_field_hashes as $hash => $field_hashes) {
5672		$data_input_id = db_fetch_cell_prepared('SELECT id FROM data_input WHERE hash = ?', array($hash));
5673
5674		if (!empty($data_input_id)) {
5675			$bad_hashes = db_fetch_assoc_prepared('SELECT *
5676				FROM data_input_fields
5677				WHERE hash NOT IN ("' . implode('","', $field_hashes) . '")
5678				AND hash != ""
5679				AND data_input_id = ?',
5680				array($data_input_id));
5681
5682			if (cacti_sizeof($bad_hashes)) {
5683				cacti_log(strtoupper($step) . ' NOTE: Repairing ' . cacti_sizeof($bad_hashes) . ' Damaged data_input_fields', false);
5684
5685				foreach($bad_hashes as $bhash) {
5686					$good_field_id = db_fetch_cell_prepared('SELECT id
5687						FROM data_input_fields
5688						WHERE hash != ?
5689						AND data_input_id = ?
5690						AND data_name = ?',
5691						array($bhash['hash'], $data_input_id, $bhash['data_name']));
5692
5693					if (!empty($good_field_id)) {
5694						cacti_log("Data Input ID $data_input_id Bad Field ID is " . $bhash['id'] . ", Good Field ID: " . $good_field_id, false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);
5695
5696						cacti_log('Executing Data Input Data Check', false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);
5697
5698						// Data Input Data
5699						$bad_mappings = db_fetch_assoc_prepared('SELECT *
5700							FROM data_input_data
5701							WHERE data_input_field_id = ?',
5702							array($bhash['id']));
5703
5704						if (cacti_sizeof($bad_mappings)) {
5705							cacti_log(strtoupper($step) . ' NOTE: Found ' . cacti_sizeof($bad_mappings) . ' Damaged data_input_fields', false);
5706							foreach($bad_mappings as $mfid) {
5707								$good_found = db_fetch_cell_prepared('SELECT COUNT(*)
5708									FROM data_input_data
5709									WHERE data_input_field_id = ?
5710									AND data_template_data_id = ?',
5711									array($good_field_id, $mfid['data_template_data_id']));
5712
5713								if ($good_found > 0) {
5714									cacti_log('Good Found for ' . $mfid['data_input_field_id'] . ', Fixing', false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);
5715
5716									db_execute_prepared('DELETE FROM data_input_data
5717										WHERE data_input_field_id = ?
5718										AND data_template_data_id = ?',
5719										array($mfid['data_input_field_id'], $mfid['data_template_data_id']));
5720								} else {
5721									cacti_log('Good NOT Found for ' . $mfid['data_input_field_id'] . ', Fixing', false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);
5722
5723									db_execute_prepared('UPDATE data_input_data
5724										SET data_input_field_id = ?
5725										WHERE data_input_field_id = ?
5726										AND data_template_data_id = ?',
5727										array($good_field_id, $mfid['data_input_field_id'], $mfid['data_template_data_id']));
5728								}
5729							}
5730						} else {
5731							cacti_log('No Bad Data Input Data Records', false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);
5732						}
5733
5734						// Data Template RRD
5735						cacti_log('Executing Data Template RRD Check', false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);;
5736
5737						$bad_mappings = db_fetch_assoc_prepared('SELECT *
5738							FROM data_template_rrd
5739							WHERE data_input_field_id = ?',
5740							array($bhash['id']));
5741
5742						if (cacti_sizeof($bad_mappings)) {
5743							cacti_log(strtoupper($step) . ' NOTE: Found ' . cacti_sizeof($bad_mappings) . ' Damaged data_template_rrd', false);
5744
5745							foreach($bad_mappings as $mfid) {
5746								$good_found = db_fetch_cell_prepared('SELECT COUNT(*)
5747									FROM data_template_rrd
5748									WHERE data_input_field_id = ?
5749									AND id = ?',
5750									array($good_field_id, $mfid['id']));
5751
5752								if ($good_found > 0) {
5753									cacti_log('Good Found for ' . $mfid['data_input_field_id'] . ', Fixing', false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);
5754
5755									db_execute_prepared('DELETE FROM data_template_rrd
5756										WHERE data_input_field_id = ?
5757										AND id = ?',
5758										array($mfid['data_input_field_id'], $mfid['id']));
5759								} else {
5760									cacti_log('Good NOT Found for ' . $mfid['data_input_field_id'] . ', Fixing', false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);
5761
5762									db_execute_prepared('UPDATE data_template_rrd
5763										SET data_input_field_id = ?
5764										WHERE data_input_field_id = ?
5765										AND id = ?',
5766										array($good_field_id, $mfid['data_input_field_id'], $mfid['id']));
5767								}
5768							}
5769						} else {
5770							cacti_log('No Bad Data Template RRD Records', false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);
5771						}
5772
5773						db_execute_prepared('DELETE FROM data_input_fields WHERE hash = ?', array($bhash['hash']));
5774					} else {
5775						cacti_log('WARNING: Could not find Cacti default matching hash for unknown system hash "' . $bhash['hash'] . '" for ' . $data_input_id . '.  No repair performed.');
5776					}
5777				}
5778			}
5779		} else {
5780			cacti_log("Could not find hash '" . $hash . "' for Data Input", false, 'WEBUI', POLLER_VERBOSITY_DEVDBG);
5781		}
5782	}
5783}
5784
5785if (isset($config['cacti_server_os']) && $config['cacti_server_os'] == 'win32' && !function_exists('posix_kill')) {
5786	function posix_kill($pid, $signal = SIGTERM) {
5787		$wmi   = new COM("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");
5788		$procs = $wmi->ExecQuery("SELECT ProcessId FROM Win32_Process WHERE ProcessId='" . $pid . "'");
5789
5790		if (cacti_sizeof($procs)) {
5791			if ($signal == SIGTERM) {
5792				foreach($procs as $proc) {
5793					$proc->Terminate();
5794				}
5795			} else {
5796				return true;
5797			}
5798		} else {
5799			return false;
5800		}
5801	}
5802}
5803
5804function is_ipaddress($ip_address = '') {
5805	/* check for ipv4/v6 */
5806	if (function_exists('filter_var')) {
5807		if (filter_var($ip_address, FILTER_VALIDATE_IP) !== false) {
5808			return true;
5809		} else {
5810			return false;
5811		}
5812	} elseif (inet_pton($ip_address) !== false) {
5813		return true;
5814	} else {
5815		return false;
5816	}
5817}
5818
5819/**
5820 * date_time_format		create a format string for date/time
5821 *
5822 * @return string returns	date time format
5823 */
5824function date_time_format() {
5825	$datechar = array(
5826		GDC_HYPHEN => '-',
5827		GDC_SLASH  => '/',
5828		GDC_DOT    => '.'
5829	);
5830
5831	/* setup date format */
5832	$date_fmt        = read_config_option('default_date_format');
5833	$dateCharSetting = read_config_option('default_datechar');
5834
5835	if (!isset($datechar[$dateCharSetting])) {
5836		$dateCharSetting = GDC_SLASH;
5837	}
5838
5839	$datecharacter = $datechar[$dateCharSetting];
5840
5841	switch ($date_fmt) {
5842		case GD_MO_D_Y:
5843			return 'm' . $datecharacter . 'd' . $datecharacter . 'Y H:i:s';
5844		case GD_MN_D_Y:
5845			return 'M' . $datecharacter . 'd' . $datecharacter . 'Y H:i:s';
5846		case GD_D_MO_Y:
5847			return 'd' . $datecharacter . 'm' . $datecharacter . 'Y H:i:s';
5848		case GD_D_MN_Y:
5849			return 'd' . $datecharacter . 'M' . $datecharacter . 'Y H:i:s';
5850		case GD_Y_MO_D:
5851			return 'Y' . $datecharacter . 'm' . $datecharacter . 'd H:i:s';
5852		case GD_Y_MN_D:
5853			return 'Y' . $datecharacter . 'M' . $datecharacter . 'd H:i:s';
5854		default:
5855			return 'Y' . $datecharacter . 'm' . $datecharacter . 'd H:i:s';
5856	}
5857}
5858
5859/**
5860 * get_cacti_version    Generic function to get the cacti version
5861 */
5862function get_cacti_version() {
5863	static $version = '';
5864
5865	if ($version == '') {
5866		$version = trim(db_fetch_cell('SELECT cacti FROM version LIMIT 1'));
5867	}
5868
5869	return $version;
5870}
5871
5872/**
5873 * get_cacti_version_text    Return the cacti version text including beta moniker
5874 */
5875function get_cacti_version_text($include_version = true) {
5876	if ($include_version) {
5877		return trim(__('Version %s %s', CACTI_VERSION, (defined('CACTI_VERSION_BETA') ? __('- Beta %s', CACTI_VERSION_BETA):'')));
5878	} else {
5879		return trim(__('%s %s', CACTI_VERSION, (defined('CACTI_VERSION_BETA') ? __('- Beta %s', CACTI_VERSION_BETA):'')));
5880	}
5881}
5882
5883/**
5884 * get_cacti_cli_version() {
5885 */
5886function get_cacti_cli_version() {
5887	$dbversion = get_cacti_version();
5888	$version = get_cacti_version_text(false);
5889	return $version . ' (DB: ' . $dbversion . ')';
5890}
5891
5892/**
5893 * cacti_version_compare - Compare Cacti version numbers
5894 */
5895function cacti_version_compare($version1, $version2, $operator = '>') {
5896	if ($version1 == 'new_install') {
5897		$version1 = CACTI_VERSION;
5898	}
5899
5900	$length   = max(cacti_sizeof(explode('.', $version1)), cacti_sizeof(explode('.', $version2)));
5901	$version1 = version_to_decimal($version1, $length);
5902	$version2 = version_to_decimal($version2, $length);
5903
5904	switch ($operator) {
5905		case '<':
5906			if ($version1 < $version2) {
5907				return true;
5908			}
5909			break;
5910		case '<=':
5911			if ($version1 <= $version2) {
5912				return true;
5913			}
5914			break;
5915		case '>=':
5916			if ($version1 >= $version2) {
5917				return true;
5918			}
5919			break;
5920		case '>':
5921			if ($version1 > $version2) {
5922				return true;
5923			}
5924			break;
5925		case '==':
5926			if ($version1 == $version2) {
5927				return true;
5928			}
5929			break;
5930		default:
5931			return version_compare($version1, $version2, $operator);
5932	}
5933	return false;
5934}
5935
5936/**
5937 * version_to_decimal - convert version string to decimal
5938 */
5939function version_to_decimal($version, $length = 1) {
5940	$newver = '';
5941	$minor  = '';
5942
5943	$parts = explode('.', $version);
5944	foreach($parts as $part) {
5945		if (is_numeric($part)) {
5946			$part = substr('00' . $part, -2);
5947			$newver .= $part;
5948		} else {
5949			$minor = substr($part, -1);
5950			$major = substr($part, 0, strlen($part)-1);
5951			$major = substr('00' . $major, -2);
5952			$newver .= $major;
5953		}
5954	}
5955
5956	if (cacti_sizeof($parts) < $length) {
5957		$i = cacti_sizeof($parts);
5958		while($i < $length) {
5959			$newver .= '00';
5960			$i++;
5961		}
5962	}
5963
5964	if ($minor != '') {
5965		$int = ord($minor);
5966	} else {
5967		$int = 0;
5968	}
5969
5970	return @hexdec($newver) * 1000 + $int;
5971}
5972
5973/**
5974 * cacti_gethostinfo - obtains the dns information for a host
5975 */
5976function cacti_gethostinfo($hostname, $type = DNS_ALL) {
5977	return dns_get_record($hostname, $type);
5978}
5979
5980/**
5981 * cacti_gethostbyname - a ip/ipv6 replacement for php's gethostbyname function
5982 */
5983function cacti_gethostbyname($hostname, $type = '') {
5984	if ($type == '') {
5985		$type = DNS_A + DNS_AAAA;
5986	}
5987
5988	if ($type != DNS_AAAA) {
5989		$host = gethostbyname($hostname);
5990		if ($host !== $hostname) {
5991			return $host;
5992		}
5993	}
5994
5995	$return = cacti_gethostinfo($hostname, $type);
5996
5997	if (cacti_sizeof($return)) {
5998		foreach($return as $record) {
5999			switch($record['type']) {
6000			case 'A':
6001				return $record['ip'];
6002				break;
6003			case 'AAAA':
6004				return $record['ipv6'];
6005				break;
6006			}
6007		}
6008	}
6009
6010	return $hostname;
6011}
6012
6013function get_nonsystem_data_input($data_input_id) {
6014	global $hash_system_data_inputs;
6015
6016	$diid = db_fetch_cell_prepared('SELECT id FROM data_input
6017		WHERE hash NOT IN ("' . implode('","', $hash_system_data_inputs) . '")
6018		AND id = ?',
6019		array($data_input_id));
6020
6021	return $diid;
6022}
6023
6024function get_rrdtool_version() {
6025	static $version = '';
6026
6027	if ($version == '') {
6028		$version = str_replace('rrd-', '', str_replace('.x', '.0', read_config_option('rrdtool_version', true)));
6029	}
6030
6031	return $version;
6032}
6033
6034function get_installed_rrdtool_version() {
6035	global $config, $rrdtool_versions;
6036	static $version = '';
6037
6038	if ($version == '') {
6039		if ($config['cacti_server_os'] == 'win32') {
6040			$shell = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' -v');
6041		} else {
6042			$shell = shell_exec(cacti_escapeshellcmd(read_config_option('path_rrdtool')) . ' -v 2>&1');
6043		}
6044
6045		$version = false;
6046		if (preg_match('/^RRDtool ([0-9.]+) /', $shell, $matches)) {
6047			foreach ($rrdtool_versions as $rrdtool_version => $rrdtool_version_text) {
6048				if (cacti_version_compare($rrdtool_version, $matches[1], '<=')) {
6049					$version = $rrdtool_version;
6050				}
6051			}
6052		}
6053	}
6054
6055	return $version;
6056}
6057
6058function get_md5_hash($path) {
6059	$md5 = 0;
6060
6061	if (db_table_exists('poller_resource_cache')) {
6062		$md5 = db_fetch_cell_prepared('SELECT md5sum
6063			FROM poller_resource_cache
6064			WHERE `path` = ?',
6065			array($path));
6066	}
6067
6068	if (empty($md5)) {
6069		if (file_exists($path)) {
6070			$md5 = md5_file($path);
6071		} else {
6072			$md5 = md5_file(dirname(__FILE__) . '/../' . $path);
6073		}
6074	}
6075
6076	return $md5;
6077}
6078
6079function get_md5_include_js($path, $async = false) {
6080	global $config;
6081
6082	if (file_exists($path)) {
6083		$npath = str_replace($config['base_path'] . '/', '', $path);
6084	} else {
6085		$npath = $path;
6086	}
6087
6088	if ($async) {
6089		return '<script type=\'text/javascript\' src=\'' . $config['url_path'] . $npath . '?' . get_md5_hash($path) . '\' async></script>' . PHP_EOL;
6090	} else {
6091		return '<script type=\'text/javascript\' src=\'' . $config['url_path'] . $npath . '?' . get_md5_hash($path) . '\'></script>' . PHP_EOL;
6092	}
6093}
6094
6095function get_md5_include_css($path) {
6096	global $config;
6097
6098	return '<link href=\''. $config['url_path'] . $path . '?' . get_md5_hash($path) . '\' type=\'text/css\' rel=\'stylesheet\'>' . PHP_EOL;
6099}
6100
6101function is_resource_writable($path) {
6102	if (empty($path)) {
6103		return false;
6104	}
6105
6106	if ($path[strlen($path)-1] == '/') {
6107		return is_resource_writable($path . uniqid(mt_rand()) . '.tmp');
6108	}
6109
6110	if (file_exists($path)) {
6111		if (($f = @fopen($path, 'a'))) {
6112			fclose($f);
6113
6114			return true;
6115		}
6116
6117		return false;
6118	}
6119
6120	if (($f = @fopen($path, 'w'))) {
6121		fclose($f);
6122		unlink($path);
6123
6124		return true;
6125	}
6126
6127	return false;
6128}
6129
6130function get_validated_theme($theme, $defaultTheme) {
6131	global $config;
6132	if (isset($theme) && strlen($theme)) {
6133		$themePath = $config['base_path'] . '/include/themes/' . $theme . '/main.css';
6134		if (file_exists($themePath)) {
6135			return $theme;
6136		}
6137	}
6138
6139	return $defaultTheme;
6140}
6141
6142function get_validated_language($language, $defaultLanguage) {
6143	if (isset($language) && strlen($language)) {
6144		return $language;
6145	}
6146
6147	return $defaultLanguage;
6148}
6149
6150function get_running_user() {
6151	global $config;
6152
6153	static $tmp_user = '';
6154
6155	if (empty($tmp_user)) {
6156		if (function_exists('posix_geteuid')) {
6157			$tmp_user = posix_getpwuid(posix_geteuid())['name'];
6158		}
6159	}
6160
6161	if (empty($tmp_user)) {
6162		$tmp_file = tempnam(sys_get_temp_dir(), 'uid'); $f_owner = '';
6163
6164		if (is_resource_writable($tmp_file)) {
6165			if (file_exists($tmp_file)) {
6166				unlink($tmp_file);
6167			}
6168
6169			file_put_contents($tmp_file, 'cacti');
6170
6171			$f_owner = fileowner($tmp_file);
6172			$f_source = 'file';
6173
6174			if (file_exists($tmp_file)) {
6175				unlink($tmp_file);
6176			}
6177		}
6178
6179		if (empty($f_owner) && function_exists('posix_getuid')) {
6180			$f_owner = posix_getuid();
6181			$f_source = 'posix';
6182		}
6183
6184		if (!empty($f_owner) && function_exists('posix_getpwuid1')) {
6185			$f_array = posix_getpwuid($f_owner);
6186			if (isset($f_array['name'])) {
6187				$tmp_user = $f_array['name'];
6188			}
6189		}
6190
6191		if (empty($tmp_user)) {
6192			exec('id -nu', $o, $r);
6193			if ($r == 0) {
6194				$tmp_user = trim($o['0']);
6195			}
6196		}
6197
6198		/*** Code left here for future development, don't think it is right ***
6199		 *
6200		if (empty($tmp_user) && !empty($f_owner) && is_readable('/etc/passwd'))
6201		{
6202			exec(sprintf('grep :%s: /etc/passwd | cut -d: -f1', (int) $uid), $o, $r);
6203			if ($r == 0) {
6204				$tmp_user = 'passwd-' . trim($o['0']);
6205			}
6206		}
6207		 */
6208
6209		// Easy way first
6210		if (empty($tmp_user)) {
6211			$user = get_current_user();
6212			if ($user != '') {
6213				$tmp_user = $user;
6214			}
6215		}
6216
6217		// Falback method
6218		if (empty($tmp_user)) {
6219			$user = getenv('USERNAME');
6220			if ($user != '') {
6221				$tmp_user = $user;
6222			}
6223
6224			if (empty($tmp_user)) {
6225				$user = getenv('USER');
6226				if ($user != '') {
6227					$tmp_user = $user;
6228				}
6229			}
6230		}
6231	}
6232
6233	return (empty($tmp_user) ? 'apache' : $tmp_user);
6234}
6235
6236function get_debug_prefix() {
6237	$dateTime = new DateTime('NOW');
6238	$dateTime = $dateTime->format('Y-m-d H:i:s.u');
6239
6240	return sprintf('<[ %s | %7d ]> -- ', $dateTime, getmypid());
6241}
6242
6243function get_client_addr($client_addr = false) {
6244
6245	$http_addr_headers = array(
6246		'X-Forwarded-For',
6247		'X-Client-IP',
6248		'X-Real-IP',
6249		'X-ProxyUser-Ip',
6250		'CF-Connecting-IP',
6251		'True-Client-IP',
6252		'HTTP_X_FORWARDED',
6253		'HTTP_X_FORWARDED_FOR',
6254		'HTTP_X_CLUSTER_CLIENT_IP',
6255		'HTTP_FORWARDED_FOR',
6256		'HTTP_FORWARDED',
6257		'HTTP_CLIENT_IP',
6258		'REMOTE_ADDR',
6259	);
6260
6261	$client_addr = false;
6262	foreach ($http_addr_headers as $header) {
6263		if (!empty($_SERVER[$header])) {
6264			$header_ips = explode(',', $_SERVER[$header]);
6265			foreach ($header_ips as $header_ip) {
6266				if (!empty($header_ip)) {
6267					if (!filter_var($header_ip, FILTER_VALIDATE_IP)) {
6268						cacti_log('ERROR: Invalid remote client IP Address found in header (' . $header . ').', false, 'AUTH', POLLER_VERBOSITY_DEBUG);
6269					} else {
6270						$client_addr = $header_ip;
6271						cacti_log('DEBUG: Using remote client IP Address found in header (' . $header . '): ' . $client_addr . ' (' . $_SERVER[$header] . ')', false, 'AUTH', POLLER_VERBOSITY_DEBUG);
6272						break 2;
6273					}
6274				}
6275			}
6276		}
6277	}
6278
6279	return $client_addr;
6280}
6281
6282function cacti_pton($ipaddr) {
6283	// Strip out the netmask, if there is one.
6284	$subnet_pos = strpos($ipaddr, '/');
6285	if ($subnet_pos) {
6286		$subnet = substr($ipaddr, $subnet_pos+1);
6287		$ipaddr = substr($ipaddr, 0, $subnet_pos);
6288	} else {
6289		$subnet = null; // No netmask present
6290	}
6291
6292	// Convert address to packed format
6293	$addr = @inet_pton($ipaddr);
6294	if ($addr === false) {
6295		return false;
6296	}
6297
6298	// Maximum netmask length = same as packed address
6299	$len = 8*strlen($addr);
6300
6301	if (!empty($subnet)) {
6302		if (!is_numeric($subnet)) {
6303			return false;
6304		} elseif ($subnet > $len) {
6305			return false;
6306		}
6307	}
6308
6309	if (!is_numeric($subnet)) {
6310		$subnet=$len;
6311	} else {
6312		$subnet=(int)$subnet;
6313	}
6314
6315	// Create a hex expression of the subnet mask
6316	$mask=str_repeat('f',$subnet>>2);
6317	switch($subnet&3) {
6318		case 3:
6319			$mask.='e';
6320			break;
6321		case 2:
6322			$mask.='c'; break;
6323		case 1:
6324			$mask.='8'; break;
6325	}
6326	$mask=str_pad($mask,$len>>2,'0');
6327
6328	// Packed representation of netmask
6329	$mask=pack('H*',$mask);
6330
6331	$result = array('ip' => $addr, 'subnet' => $mask);
6332	return $result;
6333}
6334
6335function cacti_ntop($addr) {
6336	if (empty($addr)) {
6337		return false;
6338	}
6339
6340	if (is_array($addr)) {
6341		foreach ($addr as $ip) {
6342			$addr = $ip;
6343			break;
6344		}
6345	}
6346	return @inet_ntop($addr);
6347}
6348
6349function cacti_ntoc($subnet, $ipv6 = false) {
6350	$result = false;
6351	$count = 0;
6352	foreach(str_split($subnet) as $char) {
6353		$i = ord($char);
6354		while (($i & 128) == 128) {
6355			$count++;
6356			$i = ($i << 1) % 256;
6357		}
6358	}
6359
6360	return $count;
6361}
6362
6363function cacti_ptoa($title, $addr) {
6364	// Let's display it as hexadecimal format
6365	foreach(str_split($addr) as $char) {
6366		print str_pad(dechex(ord($char)),2,'0',STR_PAD_LEFT);
6367	}
6368}
6369
6370function cacti_sizeof($array) {
6371	return ($array === false || !is_array($array)) ? 0 : sizeof($array);
6372}
6373
6374function cacti_count($array) {
6375	return ($array === false || !is_array($array)) ? 0 : count($array);
6376}
6377
6378function is_function_enabled($name) {
6379	return function_exists($name) &&
6380		!in_array($name, array_map('trim', explode(', ', ini_get('disable_functions')))) &&
6381		strtolower(ini_get('safe_mode')) != 1;
6382}
6383
6384function is_page_ajax() {
6385	if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest' ) {
6386		return true;
6387	}
6388
6389	return false;
6390}
6391
6392function raise_ajax_permission_denied() {
6393	if (is_page_ajax()) {
6394		header('HTTP/1.1 401 ' . __('Permission Denied'));
6395		print __('You are not permitted to access this section of Cacti.') . '  ' . __('If you feel that this is an error. Please contact your Cacti Administrator.');
6396		exit;
6397	}
6398}
6399
6400/**
6401 * cacti_session_start - Create a Cacti session from the settings set by the administrator
6402 *
6403 * @return - null
6404 */
6405function cacti_session_start() {
6406	global $config;
6407
6408	/* initialize php session */
6409	if (!function_exists('session_name')) {
6410		die('PHP Session Management is missing, please install PHP Session module');
6411	}
6412
6413	session_name($config['cacti_session_name']);
6414
6415	$session_restart = '';
6416	if (session_status() === PHP_SESSION_NONE) {
6417		$session_result = session_start($config['cookie_options']);
6418	} else {
6419		$session_restart = 're';
6420		$session_result  = session_start();
6421	}
6422
6423	if (!$session_result) {
6424		cacti_log('Session "' . session_id() . '" ' . $session_restart . 'start failed! ' . cacti_debug_backtrace('', false, false, 0, 1), false, 'WARNING:');
6425	}
6426}
6427
6428/**
6429 * cacti_session_close - Closes the open Cacti session if it is open
6430 * it can be re-opened afterwards in the case after a long running query
6431 *
6432 * @return - null
6433 */
6434function cacti_session_close() {
6435	session_write_close();
6436}
6437
6438/**
6439 * cacti_session_destroy - Destroys the login current session
6440 *
6441 * @return - null
6442 */
6443function cacti_session_destroy() {
6444	session_unset();
6445	session_destroy();
6446}
6447
6448/**
6449 * cacti_cookie_set - Allows for settings an arbitry cookie name and value
6450 * used for CSRF protection.
6451 *
6452 * @return - null
6453 */
6454function cacti_cookie_set($session, $val) {
6455	global $config;
6456
6457	if (isset($config['cookie_options']['cookie_domain'])) {
6458		$domain = $config['cookie_options']['cookie_domain'];
6459	} else {
6460		$domain = '';
6461	}
6462
6463	if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
6464		setcookie($session, $val, time() + 3600, $config['url_path'], $domain, true, true);
6465	} else {
6466		setcookie($session, $val, time() + 3600, $config['url_path'], $domain, false, true);
6467	}
6468}
6469
6470/**
6471 * cacti_cookie_logout - Clears the Cacti and the 'keep me logged in' cookies
6472 *
6473 * @return - null
6474 */
6475function cacti_cookie_logout() {
6476	global $config;
6477
6478	if (isset($config['cookie_options']['cookie_domain'])) {
6479		$domain = $config['cookie_options']['cookie_domain'];
6480	} else {
6481		$domain = '';
6482	}
6483
6484	setcookie(session_name(), '', time() - 3600, $config['url_path'], $domain);
6485	setcookie('cacti_remembers', '', time() - 3600, $config['url_path'], $domain);
6486
6487	unset($_COOKIE[$config['cacti_session_name']]);
6488}
6489
6490/**
6491 * cacti_cookie_session_set - Sets the cacti 'keep me logged in' cookie
6492 *
6493 * @return - null
6494 */
6495function cacti_cookie_session_set($user, $nssecret) {
6496	global $config;
6497
6498	if (isset($config['cookie_options']['cookie_domain'])) {
6499		$domain = $config['cookie_options']['cookie_domain'];
6500	} else {
6501		$domain = '';
6502	}
6503
6504	$_SESSION['cacti_remembers'] = true;
6505
6506	if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
6507		setcookie('cacti_remembers', $user . ',' . $nssecret, time()+(86400*30), $config['url_path'], $domain, true, true);
6508	} else {
6509		setcookie('cacti_remembers', $user . ',' . $nssecret, time()+(86400*30), $config['url_path'], $domain, false, true);
6510	}
6511}
6512
6513/**
6514 * cacti_cookie_session_logout - Logs out of Cacti and the remember me session
6515 *
6516 * @return - null
6517 */
6518function cacti_cookie_session_logout() {
6519	global $config;
6520
6521	if (isset($config['cookie_options']['cookie_domain'])) {
6522		$domain = $config['cookie_options']['cookie_domain'];
6523	} else {
6524		$domain = '';
6525	}
6526
6527	setcookie('cacti_remembers', '', time() - 3600, $config['url_path'], $domain);
6528}
6529
6530/**
6531 * cacti_browser_zone_set - Set the PHP timezone to the
6532 * browsers timezone.
6533 *
6534 * @return - null
6535 */
6536function cacti_browser_zone_set() {
6537	if (isset($_SESSION['sess_browser_php_tz'])) {
6538cacti_log('going there');
6539		ini_set('date.timezone', $_SESSION['sess_browser_php_tz']);
6540		putenv('TZ=' . $_SESSION['sess_browser_system_tz']);
6541	}
6542}
6543
6544/**
6545 * cacti_system_zone_set - Set the PHP timezone to the
6546 * systems timezone.
6547 *
6548 * @return - null
6549 */
6550function cacti_system_zone_set() {
6551	if (isset($_SESSION['sess_php_tz'])) {
6552cacti_log('and back again there');
6553		ini_set('date.timezone', $_SESSION['sess_php_tz']);
6554		putenv('TZ=' . $_SESSION['sess_system_tz']);
6555	}
6556}
6557
6558/**
6559 * cacti_time_zone_set - Givin an offset in minutes, attempt
6560 * to set a PHP date.timezone.  There are some oddballs that
6561 * we have to accomodate.
6562 *
6563 * @return - null
6564 */
6565function cacti_time_zone_set($gmt_offset) {
6566	$hours     = floor($gmt_offset / 60);
6567	$remaining = $gmt_offset % 60;
6568
6569	if (!isset($_SESSION['sess_php_tz'])) {
6570		$_SESSION['sess_php_tz']    = ini_get('date.timezone');
6571		$_SESSION['sess_system_tz'] = getenv('TZ');
6572	}
6573
6574	if ($remaining == 0) {
6575		putenv('TZ=GMT' . ($hours > 0 ? '-':'+') . abs($hours));
6576
6577		$php_offset = 'Etc/GMT' . ($hours > 0 ? '-':'+') . abs($hours);
6578		$sys_offset = 'GMT' . ($hours > 0 ? '-':'+') . abs($hours);
6579
6580		ini_set('date.timezone', 'Etc/GMT' . ($hours > 0 ? '-':'+') . abs($hours));
6581
6582		$_SESSION['sess_browser_system_tz'] = $sys_offset;
6583		$_SESSION['sess_browser_php_tz'] = $php_offset;
6584	} else {
6585		$time = ($hours > 0 ? '-':'+') . abs($hours) . ':' . substr('00' . $remaining, -2);
6586
6587		// Attempt to get the zone via the php function
6588		$zone = timezone_name_from_abbr('', $time);
6589
6590		if ($zone === false) {
6591			switch($time) {
6592				case '+3:30':
6593					$zone = 'IRST';
6594					break;
6595				case '+4:30':
6596					$zone = 'IRDT';
6597					break;
6598				case '+5:30':
6599					$zone = 'IST';
6600					break;
6601				case '+5:45':
6602					$zone = 'NPT';
6603					break;
6604				case '+6:30':
6605					$zone = 'CCT';
6606					break;
6607				case '+9:30':
6608					$zone = 'ACST';
6609					break;
6610				case '+10:30':
6611					$zone = 'ACDT';
6612					break;
6613				case '+8:45':
6614					$zone = 'ACWST';
6615					break;
6616				case '+12:45':
6617					$zone = 'CHAST';
6618					break;
6619				case '+13:45':
6620					$zone = 'CHADT';
6621					break;
6622				case '-3:30':
6623					$zone = 'NST';
6624					break;
6625				case '-2:30':
6626					$zone = 'NDT';
6627					break;
6628				case '-9:30':
6629					$zone = 'MART';
6630					break;
6631			}
6632
6633			if ($zone !== false) {
6634				$zone = timezone_name_from_abbr($zone);
6635			}
6636		}
6637
6638		$php_offset = $zone;
6639		$sys_offset = 'GMT' . $time;
6640
6641		putenv('TZ=GMT' . $time);
6642
6643		if ($zone != '') {
6644			ini_set('date.timezone', $zone);
6645		}
6646
6647		$_SESSION['sess_browser_system_tz'] = $sys_offset;
6648		$_SESSION['sess_browser_php_tz']    = $php_offset;
6649	}
6650}
6651
6652