<?php
/*
 +-------------------------------------------------------------------------+
 | Copyright (C) 2004-2024 The Cacti Group                                 |
 |                                                                         |
 | This program is free software; you can redistribute it and/or           |
 | modify it under the terms of the GNU General Public License             |
 | as published by the Free Software Foundation; either version 2          |
 | of the License, or (at your option) any later version.                  |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 +-------------------------------------------------------------------------+
 | Cacti: The Complete RRDtool-based Graphing Solution                     |
 +-------------------------------------------------------------------------+
 | This code is designed, written, and maintained by the Cacti Group. See  |
 | about.php and/or the AUTHORS file for specific developer information.   |
 +-------------------------------------------------------------------------+
 | http://www.cacti.net/                                                   |
 +-------------------------------------------------------------------------+
*/


/**
 * clear_auth_cookie - clears a users security token
 *
 * @return (void)
 */
function clear_auth_cookie() {
	global $config;

	if (isset($_COOKIE['cacti_remembers']) && read_config_option('auth_cache_enabled') == 'on') {
		$parts = explode(',', $_COOKIE['cacti_remembers']);

		if (cacti_sizeof($parts) == 2) {
			$user_id  = $parts[0];
			$realm_id = -1;
			$token    = $parts[1];
		} else {
			$user_id  = $parts[0];
			$realm_id = $parts[1];
			$token    = $parts[2];
		}

		// Legacy support which leaked usernames
		if (!is_numeric($user_id)) {
			$user_id = db_fetch_cell_prepared('SELECT id
				FROM user_auth
				WHERE username = ?',
				array($user_id));
		}

		if ($user_id > 0) {
			$secret = hash('sha512', $token, false);

			cacti_cookie_session_logout();

			db_execute_prepared('DELETE FROM user_auth_cache
				WHERE user_id = ?
				AND token = ?',
				array($user_id, $secret));
		}
	}
}

/**
 * set_auth_cookie - sets a users security token
 *
 * @param  (array) user is the user_auth row for the user
 *
 * @return (bool) True if token set worked, otherwise false
 */
function set_auth_cookie($user) {
	global $config;

	if (db_table_exists('user_auth_cache')) {
		clear_auth_cookie();

		$nssecret = md5($_SERVER['REQUEST_TIME'] .  mt_rand(10000,10000000)) . md5(get_client_addr());

		$secret = hash('sha512', $nssecret, false);

		db_execute_prepared('REPLACE INTO user_auth_cache
			(user_id, hostname, last_update, token)
			VALUES
			(?, ?, NOW(), ?);',
			array($user['id'], get_client_addr(), $secret));

		cacti_cookie_session_set($user['id'], $user['realm'], $nssecret);
	}
}

/**
 * check_auth_cookie - clears a users security token
 *
 * @return (int) The user of the session cookie, otherwise false
 */
function check_auth_cookie() {
	if (isset($_COOKIE['cacti_remembers']) &&
		read_config_option('auth_cache_enabled') == 'on' &&
		db_table_exists('user_auth_cache')) {

		$parts = explode(',', $_COOKIE['cacti_remembers']);

		if (cacti_sizeof($parts) == 2) {
			$user_id  = $parts[0];
			$realm_id = -1;
			$token    = $parts[1];
		} else {
			$user_id  = $parts[0];
			$realm_id = $parts[1];
			$token    = $parts[2];
		}

		// Legacy support which leaked usernames
		if (!is_numeric($user_id)) {
			$user_id = db_fetch_cell_prepared('SELECT id
				FROM user_auth
				WHERE username = ?',
				array($user_id));
		}

		if ($user_id > 0 && $user_id != get_guest_account()) {
			if ($realm_id == -1) {
				$user_info = db_fetch_row_prepared('SELECT id, realm, username
					FROM user_auth
					WHERE id = ?',
					array($user_id));
			} else {
				$user_info = db_fetch_row_prepared('SELECT id, realm, username
					FROM user_auth
					WHERE id = ?
					AND realm = ?',
					array($user_id, $realm_id));
			}

			if (cacti_sizeof($user_info)) {
				$secret = hash('sha512', $token, false);

				$found  = db_fetch_cell_prepared('SELECT user_id
					FROM user_auth_cache
					WHERE user_id = ?
					AND token = ?',
					array($user_info['id'], $secret)
				);

				if (empty($found)) {
					return false;
				} else {
					set_auth_cookie($user_info);

					cacti_log("LOGIN: User '" . $user_info['username'] . "' Authenticated via Authentication Cookie", false, 'AUTH');

					db_execute_prepared('INSERT IGNORE INTO user_log
						(username, user_id, result, ip, time)
						VALUES
						(?, ?, 2, ?, NOW())',
						array($user_info['username'], $user_info['id'], get_client_addr())
					);

					return $user_info['id'];
				}
			}
		}
	}

	return false;
}

/**
 * is_template_account - given a username or user_id test if this is a template account
 *   Template accounts could be accounts used for the administrative email, for both
 *   the guest and template accounts, or a user that is specified by a plugin as a
 *   template account.
 *
 * @param  (int|string) user_id is either the user_id or a username
 *
 * @return (bool) true if template account, false otherwise
 */
function is_template_account($user_id) {
	if (!is_numeric($user_id)) {
		$user_id = db_fetch_cell_prepared('SELECT id FROM user_auth WHERE username = ?', array($user_id));
	}

	if (empty($user_id)) {
		return false;
	}

	if (read_config_option('admin_user') == $user_id) {
		return true;
	} elseif (read_config_option('guest_user') == $user_id) {
		return true;
	} elseif (read_config_option('user_template') == $user_id) {
		return true;
	} else {
		$domain_template = db_fetch_cell_prepared('SELECT COUNT(*)
			FROM user_domains
			WHERE user_id = ?',
			array($user_id));

		if ($domain_template > 0) {
			return true;
		} else {
			$plugin_template = get_template_account($user_id);

			if ($plugin_template == $user_id) {
				return true;
			}
		}
	}

	return false;
}

/**
 * get_basic_auth_username - If basic auth is used, return the valid username
 *
 * @return (string) the new username, or false if one was not passed
 */
function get_basic_auth_username() {
	if (isset($_SERVER['PHP_AUTH_USER'])) {
		$username = str_replace("\\", "\\\\", $_SERVER['PHP_AUTH_USER']);
	} elseif (isset($_SERVER['REMOTE_USER'])) {
		$username = str_replace("\\", "\\\\", $_SERVER['REMOTE_USER']);
	} elseif (isset($_SERVER['REDIRECT_REMOTE_USER'])) {
		$username = str_replace("\\", "\\\\", $_SERVER['REDIRECT_REMOTE_USER']);
	} elseif (isset($_SERVER['HTTP_PHP_AUTH_USER'])) {
		$username = str_replace("\\", "\\\\", $_SERVER['HTTP_PHP_AUTH_USER']);
	} elseif (isset($_SERVER['HTTP_REMOTE_USER'])) {
		$username = str_replace("\\", "\\\\", $_SERVER['HTTP_REMOTE_USER']);
	} elseif (isset($_SERVER['HTTP_REDIRECT_REMOTE_USER'])) {
		$username = str_replace("\\", "\\\\", $_SERVER['HTTP_REDIRECT_REMOTE_USER']);
	} else {
		$username = false;
	}

	if ($username !== false) {
		if (strpos($username, '@') !== false) {
			$upart = explode('@', $username);
			$username = $upart[0];
		}

		/* Handle mapping basic accounts to shortform accounts.
		 * Format of map file is CSV: basic,shortform */
		$mapfile = read_config_option('path_basic_mapfile');
		if ($mapfile != '' && file_exists($mapfile) && is_readable($mapfile)) {
			$records = file($mapfile);
			$found   = false;

			if (cacti_sizeof($records)) {
				foreach($records as $r) {
					list($basic, $shortform) = str_getcsv($r);

					if (trim($basic) == $username) {
						$username = trim($shortform);
						$found    = true;

						break;
					}
				}
			}

			if (!$found) {
				cacti_log("WARNING: Username $username not found in basic mapfile.", false, 'AUTH');
			}
		}
	}

	return $username;
}

/**
 * user_copy - copies user account
 *
 * @param  (string)  $template_user - username of the user account that should be used as the template
 * @param  (string)  $new_user - new username of the account to be created/overwritten
 * @param  (int)     $template_realm - new realm of the account
 * @param  (int)     $new_realm - new realm of the account to be created, overwrite not affected, but is used for lookup
 * @param  (bool)    $overwrite - Allow overwrite of existing user, preserves username, fullname, password and realm
 * @param  (array)   $data_override - Array of user_auth field and values to override on the new user
 *
 * @return (int|bool) the new users id, or false on no copy
 */
function user_copy($template_user, $new_user, $template_realm = 0, $new_realm = 0, $overwrite = false, $data_override = array()) {
	/* ================= input validation ================= */
	input_validate_input_number($template_realm);
	input_validate_input_number($new_realm);
	/* ==================================================== */

	/* Check get template users array */
	$user_auth = db_fetch_row_prepared('SELECT *
		FROM user_auth
		WHERE username = ?
		AND realm = ?',
		array($template_user, $template_realm));

	if (!cacti_sizeof($user_auth)) {
		return false;
	}

	$template_id = $user_auth['id'];

	/* Create update/insert for new/existing user */
	$user_exist = db_fetch_row_prepared('SELECT *
		FROM user_auth
		WHERE username = ?
		AND realm = ?',
		array($new_user, $new_realm));

	if (cacti_sizeof($user_exist)) {
		if ($overwrite) {
			/* Overwrite existing user */
			$user_auth['id']            = $user_exist['id'];
			$user_auth['username']      = $user_exist['username'];
			$user_auth['password']      = $user_exist['password'];
			$user_auth['realm']         = $user_exist['realm'];
			$user_auth['full_name']     = $user_exist['full_name'];
			$user_auth['email_address'] = $user_exist['email_address'];
 			$user_auth['must_change_password'] = $user_exist['must_change_password'];
			$user_auth['enabled']       = $user_exist['enabled'];
		} else {
			/* User already exists, duplicate users are bad */
			raise_message(19);

			return false;
		}
	} else {
		/* new user */
		$user_auth['id']            = 0;
		$user_auth['username']      = $new_user;
		$user_auth['enabled']       = 'on';
		$user_auth['password']      = mt_rand(100000, 10000000);
		$user_auth['email_address'] = '';
 		$user_auth['realm']         = $new_realm;
	}

	/* Update data_override fields */
	if (is_array($data_override)) {
		foreach ($data_override as $field => $value) {
			if (isset($user_auth[$field]) && $field != 'id' && $field != 'username') {
				$user_auth[$field] = $value;
			}
		}
	}

	/* Save the user */
	$new_id = sql_save($user_auth, 'user_auth');

	/* Create/Update permissions and settings */
	if (cacti_sizeof($user_exist) && $overwrite) {
		db_execute_prepared('DELETE FROM user_auth_perms WHERE user_id = ?', array($user_exist['id']));
		db_execute_prepared('DELETE FROM user_auth_realm WHERE user_id = ?', array($user_exist['id']));
		db_execute_prepared('DELETE FROM settings_user WHERE user_id = ?', array($user_exist['id']));
		db_execute_prepared('DELETE FROM settings_tree WHERE user_id = ?', array($user_exist['id']));
	}

	$user_auth_perms = db_fetch_assoc_prepared('SELECT *
		FROM user_auth_perms
		WHERE user_id = ?',
		array($template_id));

	if (cacti_sizeof($user_auth_perms)) {
		foreach ($user_auth_perms as $row) {
			$row['user_id'] = $new_id;
			sql_save($row, 'user_auth_perms', array('user_id', 'item_id', 'type'), false);
		}
	}

	$user_auth_realm = db_fetch_assoc_prepared('SELECT *
		FROM user_auth_realm
		WHERE user_id = ?',
		array($template_id));

	if (cacti_sizeof($user_auth_realm)) {
		foreach ($user_auth_realm as $row) {
			$row['user_id'] = $new_id;
			sql_save($row, 'user_auth_realm', array('realm_id', 'user_id'), false);
		}
	}

	$settings_user = db_fetch_assoc_prepared('SELECT *
		FROM settings_user
		WHERE user_id = ?',
		array($template_id));

	if (cacti_sizeof($settings_user)) {
		foreach ($settings_user as $row) {
			$row['user_id'] = $new_id;
			sql_save($row, 'settings_user', array('user_id', 'name'), false);
		}
	}

	$settings_tree = db_fetch_assoc_prepared('SELECT *
		FROM settings_tree
		WHERE user_id = ?',
		array($template_id));

	if (cacti_sizeof($settings_tree)) {
		foreach ($settings_tree as $row) {
			$row['user_id'] = $new_id;
			sql_save($row, 'settings_tree', array('user_id', 'graph_tree_item_id'), false);
		}
	}

	/* apply group permissions for the user */
	$groups = db_fetch_assoc_prepared('SELECT group_id
		FROM user_auth_group_members
		WHERE user_id = ?',
		array($template_id));

	if (cacti_sizeof($groups)) {
		foreach($groups as $g) {
			$sql[] = '(' . $new_id . ', ' . $g['group_id'] . ')';
		}

		db_execute('INSERT IGNORE INTO user_auth_group_members
			(user_id, group_id) VALUES ' . implode(',', $sql));
	}

	api_plugin_hook_function('copy_user', array('template_id' => $template_id, 'new_id' => $new_id));

	return $new_id;
}


/**
 * user_remove - remove a user account
 *
 * @param  (int) $user_id - Id os the user account to remove
 *
 * @return (void)
 */
function user_remove($user_id) {
	/* ================= input validation ================= */
	input_validate_input_number($user_id);
	/* ==================================================== */

	/* check for guest or template user */
	$username = db_fetch_cell_prepared('SELECT username
		FROM user_auth
		WHERE id = ?',
		array($user_id));

	if ($username != get_nfilter_request_var('username')) {
		if (is_template_account($user_id)) {
			raise_message(21);
			return;
		}

		if ($user_id === get_guest_account()) {
			raise_message(21);
			return;
		}
	}

	db_execute_prepared('DELETE FROM user_auth WHERE id = ?', array($user_id));
	db_execute_prepared('DELETE FROM user_auth_realm WHERE user_id = ?', array($user_id));
	db_execute_prepared('DELETE FROM user_auth_cache WHERE user_id = ?', array($user_id));
	db_execute_prepared('DELETE FROM user_auth_perms WHERE user_id = ?', array($user_id));
	db_execute_prepared('DELETE FROM user_auth_row_cache WHERE user_id = ?', array($user_id));
	db_execute_prepared('DELETE FROM user_auth_group_members WHERE user_id = ?', array($user_id));
	db_execute_prepared('DELETE FROM settings_user WHERE user_id = ?', array($user_id));
	db_execute_prepared('DELETE FROM settings_tree WHERE user_id = ?', array($user_id));
	db_execute_prepared('DELETE FROM sessions WHERE user_id = ?', array($user_id));

	api_plugin_hook_function('user_remove', $user_id);
}

/**
 * user_disable - disable a user account
 *
 * @param  (int) $user_id - Id of the user account to disable
 *
 * @return (void)
 */
function user_disable($user_id) {
	/* ================= input validation ================= */
	input_validate_input_number($user_id);
	/* ==================================================== */

	db_execute_prepared("UPDATE user_auth SET enabled = '' WHERE id = ?", array($user_id));

	reset_user_perms($user_id);
}

/**
 * user_enable - enable a user account
 *
 * @param  (int) $user_id - Id of the user account to enable
 *
 * @return (void)
 */
function user_enable($user_id) {
	/* ================= input validation ================= */
	input_validate_input_number($user_id);
	/* ==================================================== */

	db_execute_prepared("UPDATE user_auth SET enabled = 'on' WHERE id = ?", array($user_id));

	reset_user_perms($user_id);
}

/**
 * get_auth_realms - return a list of system user authentication realms
 *
 * @param  (bool) $login - If true, we also set the local login realm
 *
 * @return (array) Array of login realms
 */
function get_auth_realms($login = false) {
	if (read_config_option('auth_method') == 4) {
		$drealms = db_fetch_assoc('SELECT domain_id, domain_name
			FROM user_domains
			WHERE enabled="on"
			ORDER BY domain_name');

		if (cacti_sizeof($drealms)) {
			if ($login) {
				$new_realms['0'] = array(
					'name' => __('Local'),
					'selected' => false
				);

				foreach($drealms as $realm) {
					$new_realms[1000+$realm['domain_id']] = array(
						'name' => $realm['domain_name'],
						'selected' => false
					);
				}

				$default_realm = db_fetch_cell('SELECT domain_id
					FROM user_domains
					WHERE defdomain=1
					AND enabled="on"');

				if (!empty($default_realm)) {
					$new_realms[1000+$default_realm]['selected'] = true;
				} else {
					$new_realms['0']['selected'] = true;
				}
			} else {
				$new_realms['0'] = __('Local');
				foreach($drealms as $realm) {
					$new_realms[1000+$realm['domain_id']] = $realm['domain_name'];
				}
			}

			return $new_realms;
		}
	}

	return array(
		'0' => ['name' => __('Local')],
		'3' => ['name' => __('LDAP')],
		'2' => ['name' => __('Web Basic')],
	);
}

/**
 * is_graph_allowed - determines whether the current user is allowed to view a certain graph
 *
 * @param  (int) $local_graph_id - the ID of the graph to check permissions for
 *
 * @return (bool) whether the current user is allowed the view the specified graph or not
 */
function is_graph_allowed($local_graph_id, $user_id = 0) {
	$rows  = 0;

	get_allowed_graphs('', '', '', $rows, $user_id, $local_graph_id);

	return ($rows > 0);
}

/**
 * auth_check_perms - A helper function to checking Tree permissions
 *
 * @param  (array) A set of tree objects
 * @param  (int)   $policy - The policy to check
 *
 * @return (bool) true if there is access else false
 */
function auth_check_perms($objects, $policy) {
	$objectSize = cacti_sizeof($objects);

	/* policy == allow AND matches = DENY */
	if ($objectSize && $policy == 1) {
		return false;
	/* policy == deny AND matches = ALLOW */
	} elseif ($objectSize && $policy == 2) {
		return true;
	/* policy == allow AND no matches = ALLOW */
	} elseif (!$objectSize && $policy == 1) {
		return true;
	/* policy == deny AND no matches = DENY */
	} elseif (!$objectSize && $policy == 2) {
		return false;
	}
}

/**
 * auth_augment_roles - A helper function to extend Cacti roles with additional realms
 *   or to add a new role.
 *
 * @param  (string) $role_name - The role to extend or add
 * @param  (array)  $files - The filenames to add to the role
 *
 * @return (void)
 */
function auth_augment_roles($role_name, $files) {
	global $user_auth_roles, $user_auth_realm_filenames;

	foreach($files as $file) {
		if (array_search($file, $user_auth_realm_filenames, true) !== false) {
			if (array_search($user_auth_realm_filenames[$file], $user_auth_roles[$role_name], true) === false) {
				$user_auth_roles[$role_name][] = $user_auth_realm_filenames[$file];
			}
		} else {
			if (isset($_SESSION['sess_auth_names'][$file])) {
				$realm_id = $_SESSION['sess_auth_names'][$file];
			} else {
				$realm_id = db_fetch_cell_prepared('SELECT id+100 AS realm
					FROM plugin_realms
					WHERE file = ?
					OR file LIKE ?
					OR file LIKE ?
					OR file LIKE ?',
					array(
						$file,
						$file . ',%',
						'%,' . $file . ',%',
						'%,' . $file
					)
				);

				if ($realm_id > 0) {
					$_SESSION['sess_auth_names'][$file] = $realm_id;
				}
			}

			if (!empty($realm_id)) {
				if (!isset($user_auth_roles[$role_name])) {
					$user_auth_roles[$role_name][] = $realm_id;
				} elseif (array_search($realm_id, $user_auth_roles[$role_name]) === false) {
					$user_auth_roles[$role_name][] = $realm_id;
				}
			}
		}
	}
}

/**
 * auth_augment_roles_byname - A helper function to extend Cacti roles with additional realms
 *   or to add a new role.
 *
 * @param  (string)  $role_name - The role to extend or add
 * @param  (string)  $auth_name - The name that must be mapped
 *
 * @return (void)
 */
function auth_augment_roles_byname($role_name, $auth_name) {
	global $user_auth_roles, $user_auth_realm_filenames;

	if (isset($_SESSION['sess_auth_names'][$auth_name])) {
		$realm_id = $_SESSION['sess_auth_names'][$auth_name];
	} else {
		$realm_id = db_fetch_cell_prepared('SELECT id+100 AS realm
			FROM plugin_realms
			WHERE display = ?',
			array($auth_name));

		if ($realm_id > 0) {
			$_SESSION['sess_auth_names'][$auth_name] = $realm_id;
		}
	}

	if (!empty($realm_id)) {
		if (!isset($user_auth_roles[$role_name])) {
			$user_auth_roles[$role_name][] = $realm_id;
		} elseif (array_search($realm_id, $user_auth_roles[$role_name]) === false) {
			$user_auth_roles[$role_name][] = $realm_id;
		}
	}
}

/**
 * is_tree_allowed - determines whether the current user is allowed to view a certain graph tree
 *
 * @param  (int)  $tree_id the ID of the graph tree to check permissions for
 * @param  (int)  If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (bool) whether the current user is allowed the view the specified graph tree or not
 */
function is_tree_allowed($tree_id, $user_id = 0) {
	if ($user_id == -1) {
		return true;
	}

	if (isset($_SESSION['sess_tree_perms'][$tree_id])) {
		return $_SESSION['sess_tree_perms'][$tree_id];
	}

	if (read_config_option('auth_method') != 0) {
		if ($user_id == 0) {
			if (isset($_SESSION['sess_user_id'])) {
				$user_id = $_SESSION['sess_user_id'];
			} else {
				$_SESSION['sess_tree_perms'][$tree_id] = false;

				return false;
			}
		}

		$policy = db_fetch_cell_prepared('SELECT policy_trees
			FROM user_auth
			WHERE id = ?',
			array($user_id));

		$trees  = db_fetch_assoc_prepared('SELECT user_id
			FROM user_auth_perms
			WHERE user_id = ?
			AND type = 2
			AND item_id = ?',
			array($user_id, $tree_id));

		if (auth_check_perms($trees, $policy)) {
			$_SESSION['sess_tree_perms'][$tree_id] = true;

			return true;
		}

		/* check for group perms */
		$groups = db_fetch_assoc_prepared("SELECT uag.policy_trees
			FROM user_auth_group AS uag
			INNER JOIN user_auth_group_members AS uagm
			ON uag.id = uagm.group_id
			WHERE uag.enabled = 'on'
			AND uagm.user_id = ?",
			array($user_id));

		if (!cacti_sizeof($groups)) {
			$_SESSION['sess_tree_perms'][$tree_id] = false;

			return false;
		}

		foreach ($groups as $g) {
			if (auth_check_perms($trees, $g['policy_trees'])) {
				$_SESSION['sess_tree_perms'][$tree_id] = true;

				return true;
			}
		}

		/* check for group trees */
		$gtrees = db_fetch_assoc_prepared("SELECT uagm.user_id
			FROM user_auth_group AS uag
			INNER JOIN user_auth_group_members AS uagm
			ON uag.id = uagm.group_id
			INNER JOIN user_auth_group_perms as uagp
			ON uagp.group_id = uag.id
			WHERE uag.enabled = 'on'
			AND uagm.user_id = ?
			AND uagp.item_id = ?",
			array($user_id, $tree_id));

		foreach ($groups as $g) {
			if (auth_check_perms($gtrees, $g['policy_trees'])) {
				$_SESSION['sess_tree_perms'][$tree_id] = true;
				return true;
			}
		}

		$_SESSION['sess_tree_perms'][$tree_id] = false;

		return false;
	} else {
		$_SESSION['sess_tree_perms'][$tree_id] = true;

		return true;
	}
}

/**
 * is_device_allowed - determines whether the current user is allowed to view a certain device
 *
 * @param  (int)  $device_id - the ID of the device to check permissions for
 * @param  (int)  If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (bool) whether the current user is allowed the view the specified device or not
 */
function is_device_allowed($device_id, $user_id = 0) {
	$total_rows = -2;
	get_allowed_devices('', '', '', $total_rows, $user_id, $device_id);
	return ($total_rows > 0);
}

/**
 * is_graph_template_allowed - determines whether the current user is allowed to view a certain graph template
 *
 * @param  (int)  $graph_template_id - The ID of the graph template to check permissions for
 * @param  (int)  If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (bool) whether the current user is allowed the view the specified graph template or not
 */
function is_graph_template_allowed($graph_template_id, $user = 0) {
	$total_rows = -2;
	get_allowed_graph_templates('', '', '', $total_rows, $user, $graph_template_id);

	return ($total_rows > 0);
}

/**
 * is_view_allowed - Returns a true or false as to whether or not a specific view type is allowed
 *   View options include 'show_tree', 'show_list', 'show_preview', 'graph_settings'
 *
 * @param  (string) $view - the view to check for permissions on
 *
 * @return (bool) True if allowed, else false
 */
function is_view_allowed($view = 'show_tree') {
	if (read_config_option('auth_method') != 0) {
		if (!isset($_SESSION['sess_user_id'])) {
			return false;
		}

		if (db_table_exists('user_auth_group')) {
			$values = array_rekey(
				db_fetch_assoc_prepared("SELECT DISTINCT $view
					FROM user_auth_group AS uag
					INNER JOIN user_auth_group_members AS uagm
					ON uag.id = uagm.user_id
					WHERE uag.enabled = 'on'
					AND uagm.user_id = ?",
					array($_SESSION['sess_user_id'])
				), $view, $view
			);

			if (isset($values[3])) {
				return false;
			} elseif (isset($values['on'])) {
				return true;
			} elseif (isset($values[2])) {
				return true;
			}
		}
		$value = db_fetch_cell_prepared("SELECT $view
			FROM user_auth
			WHERE id = ?",
			array($_SESSION['sess_user_id'])
		);

		return ($value == 'on');
	} else {
		return true;
	}
}

/**
 * is_tree_branch_empty - Given a tree id and a branch id, check if it's empty
 *
 * @param  (int)  $tree_id - The Cacti Tree id
 * @param  (int)  $parent  - The Cacti Tree branch id
 *
 * @return (bool) True if empty, else false
 */
function is_tree_branch_empty($tree_id, $parent = 0) {
	$graphs = array_rekey(
		db_fetch_assoc_prepared('SELECT local_graph_id
			FROM graph_tree_items
			WHERE graph_tree_id = ?
			AND local_graph_id > 0
			AND parent = ?',
			array($tree_id, $parent)
		), 'local_graph_id', 'local_graph_id'
	);

	$simple_perms = get_simple_graph_perms($_SESSION['sess_user_id']);

	if (cacti_sizeof($graphs) && ($simple_perms || cacti_sizeof(get_allowed_graphs('gl.id IN(' . implode(',', $graphs) . ')'))) > 0) {
		return false;
	}

	$hosts = array_rekey(
		db_fetch_assoc_prepared('SELECT host_id
			FROM graph_tree_items
			WHERE graph_tree_id = ?
			AND host_id > 0
			AND parent = ?',
			array($tree_id, $parent)
		), 'host_id', 'host_id'
	);

	$sites = array_rekey(
		db_fetch_assoc_prepared('SELECT site_id
			FROM graph_tree_items
			WHERE graph_tree_id = ?
			AND site_id > 0
			AND parent = ?',
			array($tree_id, $parent)
		), 'site_id', 'site_id'
	);

	if (!cacti_sizeof($sites)) {
		if (cacti_sizeof($hosts) && cacti_sizeof(get_allowed_devices('h.id IN(' . implode(',', $hosts) . ')'), 'description', '', -1) > 0) {
			return false;
		}
	} else {
		$site_hosts = array();
		foreach($sites as $site) {
			$site_hosts += array_rekey(
				db_fetch_assoc_prepared('SELECT id
					FROM host
					WHERE site_id = ?',
					array($site)
				), 'id', 'id'
			);
		}

		if (cacti_sizeof($site_hosts) && cacti_sizeof(get_allowed_devices('h.id IN(' . implode(',', $site_hosts) . ')'), 'description', '', -1) > 0) {
			return false;
		}
	}

	$branches = db_fetch_assoc_prepared('SELECT id, graph_tree_id
		FROM graph_tree_items
		WHERE graph_tree_id = ?
		AND parent = ?
		AND local_graph_id = 0
		AND host_id = 0',
		array($tree_id, $parent));

	if (cacti_sizeof($branches)) {
		foreach($branches as $b) {
			if (!is_tree_branch_empty($b['graph_tree_id'], $b['id'])) {
				return false;
			}
		}
	}

	return true;
}

/**
 * is_realm_allowed - Given a realm and a user, check their permissions if the
 *   admin changed a users settings, setup the case to redirect by clearing
 *   session variables so that when an admin makes a change, the user does not
 *   have to login again to receive them.
 *
 * @param  (int)      $realm      The realm to check
 * @param  (int|bool) $check_user The either false or the user id to check
 *
 * @return (bool) True if allowed, otherwise false
 */
function is_realm_allowed($realm, $check_user = false) {
	global $config;

	/* list all realms that this user has access to */
	if (read_config_option('auth_method') != 0) {
		/* if we are only checking another users permission, don't check cache */
		if ($check_user == false) {
			/* user is not set, no permissions */
			if (!isset($_SESSION['sess_user_id'])) {
				return false;
			}

			/* check to see if the admin invalidated a permission */
			if (!is_user_perms_valid($_SESSION['sess_user_id'])) {
				if (db_table_exists('user_auth_cache')) {
					$enabled = db_fetch_cell_prepared('SELECT enabled
						FROM user_auth
						WHERE id = ?',
						array($_SESSION['sess_user_id']));

					if ($enabled == '' && get_guest_account() !== $_SESSION['sess_user_id']) {
						db_execute_prepared('DELETE FROM user_auth_cache
							WHERE user_id = ?',
							array($_SESSION['sess_user_id']));

						kill_session_var('sess_user_id');
						kill_session_var('sess_user_realms');
						kill_session_var('sess_user_config_array');
						kill_session_var('sess_config_array');
						kill_session_var('sess_auth_names');
						kill_session_var('sess_tree_perms');
						kill_session_var('sess_simple_perms');
						kill_session_var('sess_simple_template_perms');

						print '<span style="display:none;">cactiLoginSuspend</span>';
						exit;
					} else {
						kill_session_var('sess_user_realms');
						kill_session_var('sess_user_config_array');
						kill_session_var('sess_config_array');
						kill_session_var('sess_auth_names');
						kill_session_var('sess_tree_perms');
						kill_session_var('sess_simple_perms');
						kill_session_var('sess_simple_template_perms');
					}

					print '<span style="display:none;">cactiRedirect</span>';
					exit;
				} else {
					kill_session_var('sess_user_realms');
					kill_session_var('sess_user_config_array');
					kill_session_var('sess_config_array');
					kill_session_var('sess_auth_names');
					kill_session_var('sess_tree_perms');
					kill_session_var('sess_simple_perms');
					kill_session_var('sess_simple_template_perms');
				}
			}

			/* if the permission is already valid, the session variable will be set */
			if (isset($_SESSION['sess_user_realms'][$realm])) {
				return $_SESSION['sess_user_realms'][$realm];
			}

			$user_id = $_SESSION['sess_user_id'];
		} else {
			$user_id = $check_user;
		}

		/**
		 * check the permissions from the table, should only happen once per login
		 * of after a permission change by the administrator.
		 */
		if (read_config_option('auth_method') != 0) {
			if (cacti_version_compare($config['cacti_db_version'], '1.0.0', '>=')) {
				$user_realm = db_fetch_cell_prepared("SELECT realm_id
					FROM user_auth_realm
					WHERE user_id = ?
					AND realm_id = ?
					UNION
					SELECT realm_id
					FROM user_auth_group_realm AS uagr
					INNER JOIN user_auth_group AS uag
					ON uag.id = uagr.group_id
					INNER JOIN user_auth_group_members AS uagm
					ON uag.id = uagm.group_id
					WHERE uag.enabled = 'on'
					AND uagr.realm_id = ?
					AND uagm.user_id = ?",
					array($user_id, $realm, $realm, $user_id));
			} else {
				$user_realm = db_fetch_cell_prepared('SELECT realm_id
					FROM user_auth_realm
					WHERE user_id = ?
					AND realm_id = ?',
					array($user_id, $realm));
			}

			if (!empty($user_realm)) {
				if ($check_user == false) {
					$_SESSION['sess_user_realms'][$realm] = true;
				} else {
					return true;
				}
			} else {
				if ($check_user == false) {
					$_SESSION['sess_user_realms'][$realm] = false;
				} else {
					return false;
				}
			}
		} else {
			if ($check_user == false) {
				$_SESSION['sess_user_realms'][$realm] = true;
			} else {
				return true;
			}
		}
	} else {
		if ($check_user == false) {
			$_SESSION['sess_user_realms'][$realm] = true;
		} else {
			return true;
		}
	}

	return $_SESSION['sess_user_realms'][$realm];
}

/**
 * get_allowed_tree_level - Get the permitted tree branch data available to the user
 *
 * @param  (int)  The tree id to check
 * @param  (int)  The branch id, 0 is the root of the tree
 * @param  (bool) Tells the function that the user is editing
 * @param  (int)  The user id to check for permissions. if 0 then check the current user
 *
 * @return (array) An array of Tree branch items that a re allowed (graphs, devices)
 */
function get_allowed_tree_level($tree_id, $parent_id, $editing = false, $user_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	$items = db_fetch_assoc_prepared('SELECT gti.id, gti.title, gti.host_id,
		gti.site_id, gti.local_graph_id, gti.host_grouping_type,
		h.description AS hostname, s.name AS sitename
		FROM graph_tree_items AS gti
		INNER JOIN graph_tree AS gt
		ON gt.id = gti.graph_tree_id
		LEFT JOIN host AS h
		ON h.id = gti.host_id
		LEFT JOIN sites AS s
		ON s.id = gti.site_id
		WHERE gti.graph_tree_id = ?
		AND gti.parent = ?
		ORDER BY gti.position ASC',
		array($tree_id, $parent_id));

	if (!$editing) {
		$i = 0;
		if (cacti_sizeof($items)) {
			foreach($items as $item) {
				if ($item['host_id'] > 0) {
					if (!is_device_allowed($item['host_id'], $user_id)) {
						unset($items[$i]);
					}
				} elseif($item['local_graph_id'] > 0) {
					if (!is_graph_allowed($item['local_graph_id'], $user_id)) {
						unset($items[$i]);
					}
				}

				$i++;
			}
		}
	}

	return $items;
}

/**
 * get_allowed_tree_content - A function that gathers items and statistics of those items
 *   that are permitted on the tree specified
 *
 * @param  (int)    The tree id to check
 * @param  (int)    The branch id, 0 is the root of the tree
 * @param  (string) The Tree SQL where when searching for specific content
 * @param  (string) The SQL Order clause to use for the sorting of items
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (array) An array of Tree branch items that a re allowed (graphs, devices)
 */
function get_allowed_tree_content($tree_id, $parent = 0, $sql_where = '', $sql_order = '', $sql_limit = '', &$total_rows = 0, $user_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if (!is_numeric($tree_id)) {
		return array();
	}

	if (!is_numeric($parent)) {
		return array();
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	if ($sql_where != '') {
		$sql_where = "WHERE gti.local_graph_id=0
			AND gti.parent=$parent
			AND gti.graph_tree_id=$tree_id
			AND (" . $sql_where . ')';
	} else {
		$sql_where = "WHERE gti.local_graph_id=0
			AND gti.parent=$parent
			AND gti.graph_tree_id=$tree_id";
	}

	$trees = array_rekey(
		get_allowed_trees(false, false, '', '', '', $total_rows, $user_id),
		'id', 'name'
	);

	if ($tree_id > 0) {
		if (cacti_sizeof($trees)) {
			$sql_where .= ' AND gt.id IN (' . implode(', ', array_keys($trees)) . ')';
		}

		$hierarchy = db_fetch_assoc("SELECT gti.graph_tree_id AS tree_id, gti.id, gti.title, gti.host_id, gti.site_id,
			gti.local_graph_id, gti.host_grouping_type, h.description AS hostname, s.name AS sitename
			FROM graph_tree_items AS gti
			INNER JOIN graph_tree AS gt
			ON gt.id = gti.graph_tree_id
			LEFT JOIN host AS h
			ON h.id = gti.host_id
			LEFT JOIN sites AS s
			ON gti.site_id=s.id
			$sql_where
			ORDER BY gti.position");
	} elseif (cacti_sizeof($trees)) {
		$hierarchy = db_fetch_assoc("SELECT gt.id AS tree_id, '0' AS id, gt.name AS title, '0' AS host_id, '0' AS site_id,
			'0' AS local_graph_id, '1' AS host_grouping_type, '' AS hostname, '' AS sitename
			FROM graph_tree AS gt
			WHERE enabled='on'
			AND gt.id IN (" . implode(', ', array_keys($trees)) . ")
			ORDER BY gt.sequence");
	}

	if (read_config_option('auth_method') != 0) {
		$new_hierarchy = array();
		if (cacti_sizeof($hierarchy)) {
			foreach($hierarchy as $h) {
				if ($h['host_id'] > 0) {
					if (is_device_allowed($h['host_id'])) {
						$new_hierarchy[] = $h;
					}
				} elseif ($h['id'] == 0) {
					if (!is_tree_branch_empty($h['tree_id'], $h['id'])) {
						if (is_tree_allowed($h['tree_id'])) {
							$new_hierarchy[] = $h;
						}
					}
				} elseif ($h['site_id'] > 0) {
					$new_hierarchy[] = $h;
				} elseif (!is_tree_branch_empty($h['tree_id'], $h['id'])) {
					$new_hierarchy[] = $h;
				}
			}
		}

		return $new_hierarchy;
	} else {
		return $hierarchy;
	}
}

/**
 * get_policies - Searches both the users and the users groups and returns a policy
 *   array to be used for determining object permissions.
 *
 * @param  (int)  If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (array) An array of policy objects comprising the users permissions
 */
function get_policies($user_id) {
	/* get policies for all user groups */
	$policies = db_fetch_assoc_prepared("SELECT uag.id, 'group' AS type, uag.name,
		uag.policy_graphs, uag.policy_hosts, uag.policy_graph_templates, uag.policy_trees
		FROM user_auth_group AS uag
		INNER JOIN user_auth_group_members AS uagm
		ON uag.id = uagm.group_id
		WHERE uag.enabled = 'on'
		AND uagm.user_id = ?",
		array($user_id));

	/* get policies for the user */
	$policies[] = db_fetch_row_prepared("SELECT id, 'user' AS type, 'user' AS name,
		policy_graphs, policy_hosts, policy_graph_templates, policy_trees
		FROM user_auth
		WHERE id = ?",
		array($user_id));

	return $policies;
}

/**
 * get_allowed_tree_header_graphs - Returns the graphs that are permitted at the branch/leaf id specified
 *
 * @param  (int)    The tree id to check
 * @param  (int)    The branch id, 0 is the root of the tree
 * @param  (string) The Tree SQL where when searching for specific content
 * @param  (string) The SQL Order clause to use for the sorting of items
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (array) Array of tree header graphs to display
 */
function get_allowed_tree_header_graphs($tree_id, $leaf_id = 0, $sql_where = '', $sql_order = 'gti.position', $sql_limit = '', &$total_rows = 0, $user_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if (!is_numeric($tree_id)) {
		return array();
	}

	if (!is_numeric($leaf_id)) {
		return array();
	}

	if ($user_id == -1) {
		$auth_method = 0;
	} else {
		$auth_method = read_config_option('auth_method');
	}

	if ($auth_method > 0 && $user_id == 0) {
		if (isset($_SESSION['sess_user_id'])) {
			$user_id = $_SESSION['sess_user_id'];
		} else {
			return array();
		}
	}

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	if ($sql_where != '') {
		$sql_where = " AND ($sql_where)";
	}

	$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;

	if (read_user_setting('hide_disabled', false, false, $user_id) == 'on') {
		$sql_where .= ($sql_where != '' ? ' AND ':'') . '(h.disabled = "" OR h.disabled IS NULL)';
	}

	$graph_auth_method = read_config_option('graph_auth_method');

	/* get policies for all groups and user */
	$policies = get_policies($user_id);

	if ($auth_method != 0) {
		$sql_where = get_policy_where($graph_auth_method, $policies, $sql_where);
	}

	$graphs = db_fetch_assoc("SELECT gti.id, gti.title, gtg.local_graph_id, h.description,
		gt.name AS template_name, gtg.title_cache, gtg.width, gtg.height, gl.host_id, h.disabled,
		gl.snmp_index, gl.snmp_query_id
		FROM graph_templates_graph AS gtg
		INNER JOIN graph_local AS gl
		ON gl.id = gtg.local_graph_id
		INNER JOIN graph_tree_items AS gti
		ON gti.local_graph_id = gl.id
		LEFT JOIN graph_templates AS gt
		ON gt.id = gl.graph_template_id
		LEFT JOIN host AS h
		ON h.id = gl.host_id
		$sql_where
		$sql_order
		$sql_limit");

	$sql = "SELECT COUNT(*)
		FROM graph_templates_graph AS gtg
		INNER JOIN graph_local AS gl
		ON gl.id=gtg.local_graph_id
		INNER JOIN graph_tree_items AS gti
		ON gti.local_graph_id=gl.id
		LEFT JOIN graph_templates AS gt
		ON gt.id=gl.graph_template_id
		LEFT JOIN host AS h
		ON h.id=gl.host_id
		$sql_where";

	$total_rows = get_total_row_data($user_id, $sql, array(), 'graph');

	return $graphs;
}

/**
 * get_allowed_graphs - Returns the graphs that are permitted by the user.  Used for table displays
 *   where users will view the graphs that they are permitted to access
 *
 * @param  (string) The SQL where when searching for specific content
 * @param  (string) The SQL Order clause to use for the sorting of graphs
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 * @param  (int)    If just searching for if a single graph is permitted, the id of that graph
 *
 * @return (array) Array of allowed graphs
 */
function get_allowed_graphs($sql_where = '', $sql_order = 'gtg.title_cache', $sql_limit = '', &$total_rows = 0, $user_id = 0, $graph_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if ($user_id == -1) {
		$auth_method = 0;
	} else {
		$auth_method = read_config_option('auth_method');
	}

	if ($auth_method > 0 && $user_id == 0) {
		if (isset($_SESSION['sess_user_id'])) {
			$user_id = $_SESSION['sess_user_id'];
		} else {
			return array();
		}
	}

	if ($sql_limit != '') {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	if ($graph_id > 0) {
		$sql_where .= ($sql_where != '' ? ' AND ' : ' ') . " gl.id = $graph_id";
	}

	if (read_user_setting('hide_disabled', false, false, $user_id) == 'on') {
		$sql_where .= ($sql_where != '' ? ' AND ':'') . '(h.disabled = "" OR h.disabled IS NULL)';
	}

	if ($sql_where != '') {
		$sql_where = "WHERE ((h.id > 0 AND h.deleted = '') OR h.id IS NULL) AND $sql_where";
	} else {
		$sql_where = "WHERE ((h.id > 0 AND h.deleted = '') OR h.id IS NULL)";
	}

	/* see if permissions are simple */
	$simple_perms = get_simple_graph_perms($user_id);

	/* in case we need to review get the graph_auth_method */
	$graph_auth_method = read_config_option('graph_auth_method');

	/* get policies for all groups and user */
	$policies = get_policies($user_id);

	if (!$simple_perms && $auth_method != 0) {
		$sql_where = get_policy_where($graph_auth_method, $policies, $sql_where);
	}

	$graphs = db_fetch_assoc("SELECT gtg.local_graph_id, h.description, gt.name AS template_name,
		gtg.title_cache, gtg.width, gtg.height, gl.snmp_index, gl.snmp_query_id, gl.host_id, h.disabled,
		IF(gl.graph_template_id=0, 0, IF(gl.snmp_query_id=0, 2, 1)) AS graph_source
		FROM graph_templates_graph AS gtg
		INNER JOIN graph_local AS gl
		ON gl.id=gtg.local_graph_id
		LEFT JOIN graph_templates AS gt
		ON gt.id=gl.graph_template_id
		LEFT JOIN host AS h
		ON h.id=gl.host_id
		$sql_where
		$sql_order
		$sql_limit");

	$sql = "SELECT COUNT(*)
		FROM graph_templates_graph AS gtg
		INNER JOIN graph_local AS gl
		ON gl.id=gtg.local_graph_id
		LEFT JOIN graph_templates AS gt
		ON gt.id=gl.graph_template_id
		LEFT JOIN host AS h
		ON h.id=gl.host_id
		$sql_where";

	if ($graph_id == 0) {
		$total_rows = get_total_row_data($user_id, $sql, array(), 'graph');
	} else {
		$total_rows = db_fetch_cell($sql);
	}

	return $graphs;
}

/**
 * get_allowed_aggregate_graphs - Returns the aggregate graphs that are permitted by the user.
 *   Used for table displays where users will view the graphs that they are permitted to access
 *
 * @param  (string) The SQL where when searching for specific content
 * @param  (string) The SQL Order clause to use for the sorting of graphs
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 * @param  (int)    If just searching for if a single graph is permitted, the id of that graph
 *
 * @return (array) Array of allowed graphs
 */
function get_allowed_aggregate_graphs($sql_where = '', $sql_order = 'gtg.title_cache', $sql_limit = '', &$total_rows = 0, $user_id = 0, $graph_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if ($user_id == -1) {
		$auth_method = 0;
	} else {
		$auth_method = read_config_option('auth_method');
	}

	if ($auth_method > 0 && $user_id == 0) {
		if (isset($_SESSION['sess_user_id'])) {
			$user_id = $_SESSION['sess_user_id'];
		} else {
			return array();
		}
	}

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	if ($graph_id > 0) {
		$sql_where .= ($sql_where != '' ? ' AND ' : ' ') . " gl.id = $graph_id";
	}

	if (read_user_setting('hide_disabled', false, false, $user_id) == 'on') {
		$sql_where .= ($sql_where != '' ? ' AND ':'') . '(h.disabled = "" OR h.disabled IS NULL)';
	}

	if ($sql_where != '') {
		$sql_where = "WHERE ((h.id > 0 AND h.deleted = '') OR h.id IS NULL) AND $sql_where";
	} else {
		$sql_where = "WHERE ((h.id > 0 AND h.deleted = '') OR h.id IS NULL)";
	}

	/* see if permissions are simple */
	$simple_perms = get_simple_graph_perms($user_id);

	/* in case we need to review get the graph_auth_method */
	$graph_auth_method = read_config_option('graph_auth_method');

	/* get policies for all groups and user */
	$policies = get_policies($user_id);

	if (!$simple_perms && $auth_method != 0) {
		$sql_where = get_policy_where($graph_auth_method, $policies, $sql_where);
	}

	$graphs = db_fetch_assoc("SELECT gtg.local_graph_id, '' AS description, gt.name AS template_name,
		gtg.title_cache, gtg.width, gtg.height, gl.snmp_index, gl.snmp_query_id, gl.host_id, h.disabled
		FROM graph_templates_graph AS gtg
		INNER JOIN (
			SELECT ag.local_graph_id AS id, gl.host_id, gl.graph_template_id,
			gl.snmp_query_id, gl.snmp_query_graph_id, gl.snmp_index
			FROM aggregate_graphs AS ag
			INNER JOIN aggregate_graphs_items AS agi
			ON ag.id=agi.aggregate_graph_id
			INNER JOIN graph_local AS gl
			ON gl.id=agi.local_graph_id
		) AS gl
		ON gl.id=gtg.local_graph_id
		LEFT JOIN graph_templates AS gt
		ON gt.id=gl.graph_template_id
		LEFT JOIN host AS h
		ON h.id=gl.host_id
		$sql_where
		GROUP BY gtg.local_graph_id
		$sql_order
		$sql_limit");

	$sql = "SELECT COUNT(DISTINCT gl.id)
		FROM graph_templates_graph AS gtg
		INNER JOIN (
			SELECT ag.local_graph_id AS id, gl.host_id, gl.graph_template_id,
			gl.snmp_query_id, gl.snmp_query_graph_id, gl.snmp_index
			FROM aggregate_graphs AS ag
			INNER JOIN aggregate_graphs_items AS agi
			ON ag.id=agi.aggregate_graph_id
			INNER JOIN graph_local AS gl
			ON gl.id=agi.local_graph_id
		) AS gl
		ON gl.id=gtg.local_graph_id
		LEFT JOIN graph_templates AS gt
		ON gt.id=gl.graph_template_id
		LEFT JOIN host AS h
		ON h.id=gl.host_id
		$sql_where";

	$total_rows = get_total_row_data($user_id, $sql, array(), 'aggregate_graph');

	return $graphs;
}

/**
 * get_simple_device_perms - Returns a boolean true or false if the user has full access to
 *   all devices in the system.  This function is used to shortcut complex queries that may
 *   take multiple seconds to return an answer.
 *
 * @param  (int)    The user id to check for permissions for
 *
 * @return (bool)   True if simple permissions are in place, otherwise false
 */
function get_simple_device_perms($user) {
	$policy_hosts = db_fetch_cell_prepared('SELECT policy_hosts
		FROM user_auth
		WHERE id = ?',
		array($user));

	$perm_count = db_fetch_cell_prepared('SELECT COUNT(*)
		FROM user_auth_perms
		WHERE user_id = ?
		AND type = 2',
		array($user));

	if ($policy_hosts == 1 && $perm_count == 0) {
		return true;
	} else {
		$policies = db_fetch_assoc_prepared('SELECT policy_hosts, COUNT(*) AS exceptions
			FROM user_auth_group AS uag
			INNER JOIN user_auth_group_perms AS uagp
			ON uag.id = uagp.group_id
			INNER JOIN user_auth_group_members AS uagm
			ON uagm.group_id = uag.id
			WHERE uagp.type = 2
			AND uagm.user_id = ?
			GROUP BY uag.id',
			array($user));

		if (cacti_sizeof($policies)) {
			foreach($policies as $p) {
				if ($p['policy_hosts'] == 1 && $p['exceptions'] == 0) {
					return true;
				}
			}
		}

		return false;
	}
}

/**
 * get_simple_graph_perms - Returns a boolean true or false if the user has full access to
 *   all graphs in the system.  This function is used to shortcut complex queries that may
 *   take multiple seconds to return an answer.
 *
 * @param  (int)    The user id to check for permissions for
 *
 * @return (bool)   True if simple permissions are in place, otherwise false
 */
function get_simple_graph_perms($user_id) {
	if (isset($_SESSION['sess_simple_perms'])) {
		return $_SESSION['sess_simple_perms'];
	}

	$policy_graphs = db_fetch_cell_prepared('SELECT policy_graphs
		FROM user_auth
		WHERE id = ?',
		array($user_id));

	$perm_count = db_fetch_cell_prepared('SELECT COUNT(*)
		FROM user_auth_perms
		WHERE user_id = ?
		AND type = 1',
		array($user_id));

	if ($policy_graphs == 1 && $perm_count == 0) {
		$_SESSION['sess_simple_perms'] = true;

		return true;
	} else {
		$policies = db_fetch_assoc_prepared('SELECT policy_graphs, COUNT(*) AS exceptions
			FROM user_auth_group AS uag
			INNER JOIN user_auth_group_perms AS uagp
			ON uag.id = uagp.group_id
			INNER JOIN user_auth_group_members AS uagm
			ON uagm.group_id = uag.id
			WHERE uagp.type = 1
			AND uagm.user_id = ?
			GROUP BY uag.id',
			array($user_id));

		if (cacti_sizeof($policies)) {
			foreach($policies as $p) {
				if ($p['policy_graphs'] == 1 && $p['exceptions'] == 0) {
					$_SESSION['sess_simple_perms'] = true;

					return true;
				}
			}
		}

		$_SESSION['sess_simple_perms'] = false;

		return false;
	}
}

/**
 * get_simple_graph_template_perms - Returns a boolean true or false if the user has full access to
 *   all graphs templates in the system.  This function is used to shortcut complex queries that may
 *   take multiple seconds to return an answer.
 *
 * @param  (int)    The user id to check for permissions for
 *
 * @return (bool)   True if simple permissions are in place, otherwise false
 */
function get_simple_graph_template_perms($user_id) {
	if (isset($_SESSION['sess_simple_template_perms'])) {
		return $_SESSION['sess_simple_template_perms'];
	}

	$policy_graph_templates = db_fetch_cell_prepared('SELECT policy_graph_templates
		FROM user_auth
		WHERE id = ?',
		array($user_id));

	$perm_count = db_fetch_cell_prepared('SELECT COUNT(*)
		FROM user_auth_perms
		WHERE user_id = ?
		AND type = 4',
		array($user_id));

	if ($policy_graph_templates == 1 && $perm_count == 0) {
		$_SESSION['sess_simple_template_perms'] = true;

		return true;
	} else {
		$policies = db_fetch_assoc_prepared('SELECT policy_graph_templates, COUNT(*) AS exceptions
			FROM user_auth_group AS uag
			INNER JOIN user_auth_group_perms AS uagp
			ON uag.id = uagp.group_id
			INNER JOIN user_auth_group_members AS uagm
			ON uagm.group_id = uag.id
			WHERE uagp.type = 4
			AND uagm.user_id = ?
			GROUP BY uag.id',
			array($user_id));

		if (cacti_sizeof($policies)) {
			foreach($policies as $p) {
				if ($p['policy_graph_templates'] == 1 && $p['exceptions'] == 0) {
					$_SESSION['sess_simple_template_perms'] = true;

					return true;
				}
			}
		}

		$_SESSION['sess_simple_template_perms'] = false;

		return false;
	}
}

/**
 * get_allowed_graph_templates - returns the list of Graph Templates that the user is allowed
 *   To access.  This function is generally intended for both listbox and table displays.
 *
 * @param  (string) The SQL where when searching for specific content
 * @param  (string) The SQL Order clause to use for the sorting of graphs
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 * @param  (int)    If just searching for if a single graph template is permitted, the id of that graph template
 *
 * @return (array)  An array of permitted Graph Templates
 */
function get_allowed_graph_templates($sql_where = '', $sql_order = 'gt.name', $sql_limit = '', &$total_rows = 0, $user_id = 0, $graph_template_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if ($user_id == -1) {
		$auth_method = 0;
	} else {
		$auth_method = read_config_option('auth_method');
	}

	if ($user_id == 0) {
		if (isset($_SESSION['sess_user_id'])) {
			$user_id = $_SESSION['sess_user_id'];
		} else {
			return array();
		}
	}

	// For timing
	$start = microtime(true);

	$init_rows = $total_rows;

	$templates = array();

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	if ($graph_template_id > 0) {
		$sql_where .= ($sql_where != '' ? ' AND ' : ' ') . "(gl.graph_template_id = $graph_template_id)";
	}

	$sql_where = 'WHERE ' . ($sql_where != '' ? $sql_where . ' AND ':' ') . '(gt.id IS NOT NULL) ';

	/* see if permissions are simple */
	$simple_perms = get_simple_graph_perms($user_id);

	/* in case we need to review get the graph_auth_method */
	$graph_auth_method = read_config_option('graph_auth_method');

	/* get policies for all groups and user */
	$policies = get_policies($user_id);

	/* short circuit if we don't have a user */
	if ($auth_method > 0 && $user_id == 0) {
		return array();
	}

	if (!$simple_perms && $auth_method != 0) {
		$sql_where = get_policy_where($graph_auth_method, $policies, $sql_where);
	}

	if ($total_rows != -2) {
		$templates = db_fetch_assoc("SELECT gt.id, gt.name, COUNT(*) AS graphs
			FROM graph_local AS gl
			LEFT JOIN graph_templates AS gt
			ON gt.id = gl.graph_template_id
			LEFT JOIN host AS h
			ON h.id = gl.host_id
			$sql_where
			GROUP BY gl.graph_template_id
			$sql_order
			$sql_limit");
	}

	if ($total_rows >= 0 || $total_rows == -2) {
		$sql = "SELECT COUNT(DISTINCT gl.graph_template_id) AS id
			FROM graph_local AS gl
			LEFT JOIN graph_templates AS gt
			ON gt.id = gl.graph_template_id
			LEFT JOIN host AS h
			ON h.id = gl.host_id
			$sql_where";

		if ($graph_template_id == 0) {
			$total_rows = get_total_row_data($user_id, $sql, array(), 'graph');
		} else {
			$total_rows = db_fetch_cell($sql);
		}
	}

	// For timing
	$end = microtime(true);

	cacti_log(sprintf('The Get Templates total time was %4.2f', $end - $start), false, 'AUTH', POLLER_VERBOSITY_DEBUG);

	if ($templates === false) {
		$templates = array();
	}

	return $templates;
}

/**
 * get_policy_join_select - Parse the policies in order to visually display user permissions
 *
 * @param  (array) $policies  The list of user and group policies.  Will be reversed to
 *   show user permissions first.
 *
 * @return (array)  Array containing both $sql_select and $sql_join
 */
function get_policy_join_select($policies) {
	$sql_join   = '';
	$sql_select = '';

	$i = 1;
	$j = 1;
	foreach($policies as $p) {
		$sql_join   .= "LEFT JOIN (SELECT * FROM user_auth_" . ($p['type'] == 'user' ? '' : 'group_') . "perms WHERE " . $p['type'] . '_id = ' . $p['id'] . ") AS uap$j ON (gl.id = uap$j.item_id AND uap$j.type = 1) ";
		$sql_select .= ($sql_select != '' ? ', ' : '') . "uap$j." . $p['type'] . "_id AS graph$i";
		$j++;

		$sql_join   .= 'LEFT JOIN (SELECT * FROM user_auth_' . ($p['type'] == 'user' ? '' : 'group_') . "perms WHERE " . $p['type'] . '_id = ' . $p['id'] . ") AS uap$j ON (gl.host_id = uap$j.item_id AND uap$j.type = 3) ";
		$sql_select .= ($sql_select != '' ? ', ' : '') . "uap$j." . $p['type'] . "_id AS device$i";
		$j++;

		$sql_join   .= 'LEFT JOIN (SELECT * FROM user_auth_' . ($p['type'] == 'user' ? '' : 'group_') . "perms WHERE " . $p['type'] . '_id = ' . $p['id'] . ") AS uap$j ON (gl.graph_template_id = uap$j.item_id AND uap$j.type = 4) ";
		$sql_select .= ($sql_select != '' ? ', ' : '') . "uap$j." . $p['type'] . "_id AS template$i";
		$j++;

		$i++;
	}

	return array('sql_select' => $sql_select, 'sql_join' => $sql_join);
}

/**
 * get_policy_where - Parse the policies in order to downselect matching graphs
 *   without the use of the SQL Having clause which is very inefficient
 *
 * @param  (int)    $graph_auth_method - The graph auth method: permissive, restrictive, device, graph_template
 * @param  (array)  $policies - The list of user and group policies.  Will be reversed to
 * @param  (string) $sql_where - The SQL where filter provided by the calling function.
 *
 * @return (string) - Updated sql_where value
 */
function get_policy_where($graph_auth_method, $policies, $sql_where) {
	if ($graph_auth_method == 1) {
		// Policy Rows include
		// id, type (group|user), policy_graphs, policy_hosts, policy_graph_templates

		// Table user_auth_perms has user_id, item_id, type
		// Types can be:
		// 1 - Graph
		// 2 - Trees
		// 3 - Device
		// 4 - Graph Template

		// Policies are
		// 1 - Default Allow, all exceptions are denied
		// 2 - Default Deny, all exceptions are allowed

		// Permissive means access to the graph, or access to either the host or template

		$sql_where .= ($sql_where != '' ? ' AND (':'WHERE (');

		foreach($policies as $index => $p) {
			$sql_where .= ($index > 0 ? ' OR (':'(');

			if ($p['type'] == 'user') {
				if ($p['policy_graphs'] == 1) {
					$sql_where .= 'gl.id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 1)';
				} else {
					$sql_where .= 'gl.id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 1)';
				}

				$sql_where .= ' OR ';

				if ($p['policy_hosts'] == 1) {
					$sql_where .= 'h.id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 3)';
				} else {
					$sql_where .= 'h.id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 3)';
				}

				$sql_where .= ' OR ';

				if ($p['policy_graph_templates'] == 1) {
					$sql_where .= 'gl.graph_template_id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 4)';
				} else {
					$sql_where .= 'gl.graph_template_id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 4)';
				}
			} else {
				if ($p['policy_graphs'] == 1) {
					$sql_where .= 'gl.id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 1)';
				} else {
					$sql_where .= 'gl.id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 1)';
				}

				$sql_where .= ' OR ';

				if ($p['policy_hosts'] == 1) {
					$sql_where .= 'h.id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 3)';
				} else {
					$sql_where .= 'h.id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 3)';
				}

				$sql_where .= ' OR ';

				if ($p['policy_graph_templates'] == 1) {
					$sql_where .= 'gl.graph_template_id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 4)';
				} else {
					$sql_where .= 'gl.graph_template_id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 4)';
				}
			}

			$sql_where .= ')';
		}

		$sql_where .= ')';
	} elseif ($graph_auth_method == 2) {
		// Policy Rows include
		// id, type (group|user), policy_graphs, policy_hosts, policy_graph_templates

		// Table user_auth_perms has user_id, item_id, type
		// Types can be:
		// 1 - Graph
		// 2 - Trees
		// 3 - Device
		// 4 - Graph Template

		// Policies are
		// 1 - Default Allow, all exceptions are denied
		// 2 - Default Deny, all exceptions are allowed

		// Restrictive means access to the graph, or access to both the host and template

		$sql_where .= ($sql_where != '' ? ' AND (':'WHERE (');

		foreach($policies as $index => $p) {
			$sql_where .= ($index == 0 ? '((':' OR ((');

			if ($p['type'] == 'user') {
				if ($p['policy_graphs'] == 1) {
					$sql_where .= 'gl.id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 1)';
				} else {
					$sql_where .= 'gl.id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 1)';
				}

				$sql_where .= ') OR (';

				if ($p['policy_hosts'] == 1) {
					$sql_where .= 'h.id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 3)';
				} else {
					$sql_where .= 'h.id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 3)';
				}

				$sql_where .= ' AND ';

				if ($p['policy_graph_templates'] == 1) {
					$sql_where .= ' gl.graph_template_id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 4)';
				} else {
					$sql_where .= ' gl.graph_template_id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 4)';
				}

				$sql_where .= ')';
			} else {
				if ($p['policy_graphs'] == 1) {
					$sql_where .= 'gl.id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 1)';
				} else {
					$sql_where .= 'gl.id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 1)';
				}

				$sql_where .= ') OR (';

				if ($p['policy_hosts'] == 1) {
					$sql_where .= 'h.id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 3)';
				} else {
					$sql_where .= 'h.id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 3)';
				}

				$sql_where .= ' AND ';

				if ($p['policy_graph_templates'] == 1) {
					$sql_where .= 'gl.graph_template_id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 4)';
				} else {
					$sql_where .= 'gl.graph_template_id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 4)';
				}

				$sql_where .= ')';
			}

			$sql_where .= ')';
		}

		$sql_where .= ')';
	} elseif ($graph_auth_method == 3) {
		// Policy Rows include
		// id, type (group|user), policy_graphs, policy_hosts, policy_graph_templates

		// Table user_auth_perms has user_id, item_id, type
		// Types can be:
		// 1 - Graph
		// 2 - Trees
		// 3 - Device
		// 4 - Graph Template

		// Policies are
		// 1 - Default Allow, all exceptions are denied
		// 2 - Default Deny, all exceptions are allowed

		$sql_where .= ($sql_where != '' ? ' AND (':'WHERE (');

		foreach($policies as $index => $p) {
			$sql_where .= ($index == 0 ? '((' : ') OR ((');

			if ($p['type'] == 'user') {
				if ($p['policy_graphs'] == 1) {
					$sql_where .= 'gl.id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 1)';
				} else {
					$sql_where .= 'gl.id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 1)';
				}

				$sql_where .= ') OR (';

				if ($p['policy_hosts'] == 1) {
					$sql_where .= 'h.id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 3)';
				} else {
					$sql_where .= 'h.id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 3)';
				}
			} else {
				if ($p['policy_graphs'] == 1) {
					$sql_where .= 'gl.id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 1)';
				} else {
					$sql_where .= 'gl.id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 1)';
				}

				$sql_where .= ') OR (';

				if ($p['policy_hosts'] == 1) {
					$sql_where .= 'h.id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 3)';
				} else {
					$sql_where .= 'h.id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 3)';
				}
			}

			$sql_where .= ')';
		}

		$sql_where .= '))';
	} elseif ($graph_auth_method == 4) {
		// Policy Rows include
		// id, type (group|user), policy_graphs, policy_hosts, policy_graph_templates

		// Table user_auth_perms has user_id, item_id, type
		// Types can be:
		// 1 - Graph
		// 2 - Trees
		// 3 - Device
		// 4 - Graph Template

		// Policies are
		// 1 - Default Allow, all exceptions are denied
		// 2 - Default Deny, all exceptions are allowed

		$sql_where .= ($sql_where != '' ? ' AND (':'WHERE (');

		foreach($policies as $index => $p) {
			$sql_where .= ($index == 0 ? '((' : ') OR ((');

			if ($p['type'] == 'user') {
				if ($p['policy_graphs'] == 1) {
					$sql_where .= 'gl.id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 1)';
				} else {
					$sql_where .= 'gl.id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 1)';
				}

				$sql_where .= ') OR (';

				if ($p['policy_graph_templates'] == 1) {
					$sql_where .= 'gl.graph_template_id NOT IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 4)';
				} else {
					$sql_where .= 'gl.graph_template_id IN (SELECT item_id FROM user_auth_perms WHERE user_id = ' . $p['id'] . ' AND type = 4)';
				}
			} else {
				if ($p['policy_graphs'] == 1) {
					$sql_where .= 'gl.id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 1)';
				} else {
					$sql_where .= 'gl.id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 1)';
				}

				$sql_where .= ') OR (';

				if ($p['policy_graph_templates'] == 1) {
					$sql_where .= 'gl.graph_template_id NOT IN (SELECT item_id FROM user_auth_group_perms WHERE group_id = ' . $p['id'] . ' AND type = 4)';
				} else {
					$sql_where .= 'gl.graph_template_id IN (SELECT item_id FROM user_auth_group_perms WHERE group_id =' . $p['id'] . ' AND type = 4)';
				}
			}

			$sql_where .= ')';
		}

		$sql_where .= '))';
	}

	return $sql_where;
}

/**
 * get_permission_string - get the effective permission string for the graph in question.  The
 *   logic for this is somewhat complex, but understandable.  First, the $graph object will include
 *   three columns generally graphX, deviceX, and templateX for each of the user or groups in the collection.
 *   The way we assign a restrictive or permissive value is based upon the graph permission setting
 *   in Cacti, but also whether or not the default access for the object type is either 'Allow' or 'Deny'.
 *
 *   - If the 'default access' for the object type is 'Deny', then a numeric value in userX means
 *     the user has permission to an object.
 *   - If the default access for the object is 'Allow', then a numeric
 *     value in userX means that the object is blocked.
 *
 *   Again, each of the graphX, deviceX, and templateX will always come in three's.  They equate to:
 *
 *   - graphX    - The user does or does not have permission to the Graph at the Graph Level
 *   - deviceX   - The user does or does not have permission to the Graph at the Device Level
 *   - templateX - The user does or does not have permission to the Graph at the Graph Template Level
 *
 *   Then, the effective permission are calculated by the Graph Permission Model in Cacti.  They are
 *
 *   - Permissive - If the user has access to the Graph, the Device, or Graph Template, then the
 *     user will have access to the Graph.
 *
 *   - Restrictive - If the user has access to the Graph, or both the Device and Graph Template,
 *     then the user will have access to the Graph
 *
 *   - Device - If the user has access to the Graph, or the Device, then the user will have
 *     access to the Graph
 *
 *   - Graph Template - If the user has access to the Graph, or the Graph Template, then the user
 *     will have access to the Graph.
 *
 *   This function will apply this logic, and then respond to the user a 'Granted' or 'Restricted'
 *   column value, and a Tooltip, that shows how the permissions were evaluated.  In other words
 *   why was the user either permitted to or denied access to the Graph.
 */
function get_permission_string(&$graph, &$policies) {
	$grantStr   = '';
	$rejectStr  = '';
	$reasonStr  = '';
	$drejectStr = '';

	// Methods:
	// 1 - Permissive
	// 2 - Restrictive
	// 3 - Device
	// 4 - Graph Template
	$method = read_config_option('graph_auth_method');

	if ($graph['disabled'] == 'on' && read_user_setting('hide_disabled', false, false, get_request_var('user_id'))) {
		$drejectStr .= __esc('Device:(Hide Disabled)');
	}

	// Policies:
	// 1 - Default Allow All - Numeric means blocked
	// 2 - Default Deny All  - Numeric means allowed

	$i = 1;
	foreach($policies as $p) {
		$allowed  = 0;
		$rejected = 0;

		// Perform the Graph Check first
		// If a user has access at the Graph Access, they always get Access
		if ($p['policy_graphs'] == 1) {
			// Default is to allow
			if (empty($graph["graph$i"])) {
				// Allow the access at the level
				$grantStr .= ($grantStr != '' ? ', ':'') . __esc('Graph:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
			} else {
				$rejectStr .= ($rejectStr != '' ? ', ':'') . __esc('Graph:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
				$rejected++;
			}
		} else {
			// Default is to Deny
			if (!empty($graph["graph$i"])) {
				$grantStr .= ($grantStr != '' ? ', ':'') . __esc('Graph:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
			} else {
				$rejectStr .= ($rejectStr != '' ? ', ':'') . __esc('Graph:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
				$rejected++;
			}
		}

		/**
		 * Now we check at the Device and Graph Template Level.  Here the permission get a bit more dicey, so we will
		 * Use case logic to make it read more simply.
		 */
		switch($method) {
			case 1: // Permissive
				if ($p['policy_hosts'] == 1) {
					if (empty($graph["device$i"])) {
						$grantStr .= ($grantStr != '' ? ', ':'') . __esc('Device:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					} else {
						$rejected++;
					}
				} else {
					if (!empty($graph["device$i"])) {
						$grantStr = $grantStr . ($grantStr != '' ? ', ':'') . __esc('Device:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					} else {
						$rejected++;
					}
				}

				if ($p['policy_graph_templates'] == 1) {
					if (empty($graph["template$i"])) {
						$grantStr .= ($grantStr != '' ? ', ':'') . __esc('Template:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					} else {
						$rejected++;
					}
				} else {
					if (!empty($graph["template$i"])) {
						$grantStr .= ($grantStr != '' ? ', ':'') . __esc('Template:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					} else {
						$rejected++;
					}
				}

				if ($rejected == 3) {
					$rejectStr .= ($rejectStr != '' ? ', ':'') . __esc('Graph+Device+Template:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
				}

				break;
			case 2: // Restrictive
				$allowed  = 0;
				$rejected = 0;

				if ($p['policy_hosts'] == 1) {
					if (empty($graph["device$i"])) {
						$allowed++;
					} else {
						$rejected++;
					}
				} else {
					if (!empty($graph["device$i"])) {
						$allowed++;
					} else {
						$rejected++;
					}
				}

				if ($p['policy_graph_templates'] == 1) {
					if (empty($graph["template$i"])) {
						$allowed++;
					} else {
						$rejected++;
					}
				} else {
					if (!empty($graph["template$i"])) {
						$rejected++;
					} else {
						$allowed++;
					}
				}

				if ($allowed == 2) {
					$grantStr = $grantStr . ($grantStr != '' ? ', ':'') . __esc('Device+Template:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
				} else {
					$rejectStr = $rejectStr . ($rejectStr != '' ? ', ':'') . __esc('Device+Template:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
				}

				break;
			case 3: // Device
				if ($p['policy_hosts'] == 1) {
					if (empty($graph["device$i"])) {
						$grantStr = $grantStr . ($grantStr != '' ? ', ':'') . __esc('Device:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					} else {
						$rejectStr = $rejectStr . ($rejectStr != '' ? ', ':'') . __esc('Device:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					}
				} else {
					if (!empty($graph["device$i"])) {
						$grantStr = $grantStr . ($grantStr != '' ? ', ':'') . __esc('Device:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					} else {
						$rejectStr = $rejectStr . ($rejectStr != '' ? ', ':'') . __esc('Device:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					}
				}

				break;
			case 4: // Graph Template
				if ($p['policy_graph_templates'] == 1) {
					if (empty($graph["template$i"])) {
						$grantStr = $grantStr . ($grantStr != '' ? ', ':'') . __esc('Template:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					} else {
						$rejectStr = $rejectStr . ($rejectStr != '' ? ', ':'') . __esc('Template:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					}
				} else {
					if (!empty($graph["template$i"])) {
						$grantStr = $grantStr . ($grantStr != '' ? ', ':'') . __esc('Template:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					} else {
						$rejectStr = $rejectStr . ($rejectStr != '' ? ', ':'') . __esc('Template:(%s%s)', ucfirst($p['type']), ($p['type'] != 'user' ? '/' . $p['name']:''));
					}
				}

				break;
		}

		$i++;
	}

	$permStr = '';

	if ($drejectStr != '') {
		$reasonStr .= ($reasonStr != '' ? ', ':'') . __esc('Restricted By: ') . $drejectStr;
	}

	if ($grantStr != '') {
		$reasonStr .= ($reasonStr != '' ? ', ':'') . __esc('Granted By: ') . trim($grantStr, ',');

		if ($rejectStr != '') {
			$reasonStr .= ', ' . __esc('Restricted By: ') . trim($rejectStr, ',');
		}

		if ($drejectStr == '') {
			$permStr = "<span data-tooltip='" . trim($reasonStr) . "' class='accessGranted'>" . __('Granted') . '</span>';
		} else {
			$permStr = "<span data-tooltip='" . trim($reasonStr) . "' class='accessRestricted'>" . __('Restricted') . '</span>';
		}
	} elseif ($rejectStr != '') {
		$reasonStr .= ($reasonStr != '' ? ', ':'') . __esc('Restricted By: ') . trim($rejectStr, ',');

		$permStr   = "<span data-tooltip='" . $reasonStr . "' class='accessRestricted'>" . __('Restricted') . '</span>';
	} else {
		$permStr = __('Unknown');
	}

	return $permStr;
}

/**
 * get_allowed_trees - returns the list of Trees that the user is allowed
 *   To access.  This function is generally intended for both listbox and table displays as
 *   well as to build out the tree for a user.
 *
 * @param  (bool)   Is the Tree in Edit mode or not
 * @param  (bool)   Return either the SQL used to get the values or the values
 * @param  (string) The SQL Order clause to use for the sorting of graphs
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 * @param  (int)    If just searching for if a single graph template is permitted, the id of that graph template
 *
 * @return (string|array)  An array of permitted Trees or the SQL to gather them
 */
function get_allowed_trees($edit = false, $return_sql = false, $sql_where = '', $sql_order = 'name', $sql_limit = '', &$total_rows = 0, $user_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	if ($user_id == -1) {
		$auth_method = 0;
	} else {
		$auth_method = read_config_option('auth_method');
	}

	if ($auth_method != 0) {
		if ($user_id == 0) {
			if (isset($_SESSION['sess_user_id'])) {
				$user_id = $_SESSION['sess_user_id'];
			} else {
				return array();
			}
		}

		/* get policies for all groups and user */
		$policies = db_fetch_assoc_prepared("SELECT uag.id, 'group' AS type, policy_trees FROM user_auth_group AS uag
			INNER JOIN user_auth_group_members AS uagm
			ON uag.id = uagm.group_id
			WHERE uag.enabled = 'on'
			AND uagm.user_id = ?",
			array($user_id));

		$policies[] = db_fetch_row_prepared("SELECT id, 'user' as type, policy_trees FROM user_auth WHERE id = ?", array($user_id));

		$i          = 0;
		$sql_join   = '';
		$sql_where1 = '';

		foreach($policies as $policy) {
			if ($policy['policy_trees'] == '1') {
				$sql_where1 .= ($sql_where1 != '' ? ' OR':'') . " uap$i." . $policy['type'] . "_id IS NULL";
			} elseif ($policy['policy_trees'] == '2') {
				$sql_where1 .= ($sql_where1 != '' ? ' OR':'') . " uap$i." . $policy['type'] . "_id IS NOT NULL";
			}

			$sql_join .= 'LEFT JOIN user_auth_' . ($policy['type'] == 'group' ? 'group_':'') . "perms AS uap$i
				ON (gt.id=uap$i.item_id AND uap$i.type=2 AND uap$i." . $policy['type'] . '_id=' . $policy['id'] . ') ';

			$i++;
		}

		if ($sql_where != '') {
			$sql_where = 'WHERE ' . ($edit == false ? '(gt.enabled="on") AND ':'') . '(' . $sql_where . ') AND (' . $sql_where1 . ')';
		} else {
			$sql_where = 'WHERE ' . ($edit == false ? '(gt.enabled="on") AND ':'') . '(gt.enabled="on") AND (' . $sql_where1 . ')';
		}

		$sql = "SELECT id, name
			FROM graph_tree AS gt
			$sql_join
			$sql_where
			$sql_order
			$sql_limit";

		if ($return_sql) {
			return $sql;
		} else {
			$trees = db_fetch_assoc($sql);

			$sql = "SELECT COUNT(gt.id)
				FROM graph_tree AS gt
				$sql_join
				$sql_where";

			$total_rows = get_total_row_data($user_id, $sql, array(), 'tree');
		}
	} else {
		if ($sql_where != '') {
			$sql_where = "WHERE enabled='on' AND $sql_where";
		} else {
			$sql_where = "WHERE enabled='on'";
		}

		if ($return_sql) {
			return "SELECT id, name FROM graph_tree $sql_where $sql_order";
		} else {
			$trees = db_fetch_assoc("SELECT id, name FROM graph_tree AS gt $sql_where $sql_order");

			$sql = "SELECT COUNT(*) FROM graph_tree AS gt $sql_where";

			$total_rows = get_total_row_data($user_id, $sql, array(), 'tree');
		}
	}

	return $trees;
}

/**
 * get_allowed_branches - returns the list of Tree branches that the user is allowed
 *   To access.  This function is generally intended for both listbox and table displays as
 *   well as to build out the tree for a user.
 *
 * @param  (bool)   Is the Tree in Edit mode or not
 * @param  (string) The SQL Where used to get the values or the values
 * @param  (string) The SQL Order clause to use for the sorting of branches
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (array)  An array of permitted Tree branches
 */
function get_allowed_branches($sql_where = '', $sql_order = 'name', $sql_limit = '', &$total_rows = 0, $user_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	// suppress total rows
	$total_rows = -1;

	if ($user_id == -1) {
		$auth_method = 0;
	} else {
		$auth_method = read_config_option('auth_method');
	}

	if ($auth_method > 0 && $user_id == 0) {
		if (isset($_SESSION['sess_user_id'])) {
			$user_id = $_SESSION['sess_user_id'];
		} else {
			return array();
		}
	}

	$simple_perms = get_simple_device_perms($user_id);

	/* get policies for all groups and user */
	$policies = get_policies($user_id);

	$sql_hosts_where = '';

	if (!$simple_perms) {
		$hosts = get_allowed_devices('', 'description', '', $total_rows);

		if (cacti_sizeof($hosts)) {
			$hosts = array_rekey($hosts, 'id', 'description');
		}

		$tree_hosts = db_fetch_assoc("SELECT DISTINCT h.id, h.description
			FROM graph_tree_items AS gti
			INNER JOIN graph_tree AS gt
			ON gti.graph_tree_id = gt.id
			INNER JOIN host AS h
			ON gti.host_id = h.id
			WHERE gti.host_id > 0
			AND $sql_where", false);

		if (cacti_sizeof($tree_hosts)) {
			$tree_hosts = array_rekey($tree_hosts, 'id', 'description');
		}

		$hosts = array_intersect_key($hosts, $tree_hosts);

		if (cacti_sizeof($hosts) > 0) {
			$sql_hosts_where =  'AND h.id IN (' . implode(',', array_keys($hosts)) . ')';
		}
	}

	if ($auth_method != 0) {
		$i          = 0;
		$sql_join   = '';
		$sql_where1 = '';

		foreach($policies as $policy) {
			if ($policy['policy_trees'] == '1') {
				$sql_where1 .= ($sql_where1 != '' ? ' OR':'') . " uap$i." . $policy['type'] . "_id IS NULL";
			} elseif ($policy['policy_trees'] == '2') {
				$sql_where1 .= ($sql_where1 != '' ? ' OR':'') . " uap$i." . $policy['type'] . "_id IS NOT NULL";
			}

			$sql_join .= 'LEFT JOIN user_auth_' . ($policy['type'] == 'group' ? 'group_':'') . "perms AS uap$i
				ON (gt.id=uap$i.item_id AND uap$i.type=2 AND uap$i." . $policy['type'] . '_id=' . $policy['id'] . ') ';

			$i++;
		}

		if ($sql_where != '') {
			$sql_where = 'WHERE gt.enabled="on" AND (' . $sql_where . ')' . ($sql_where1 != '' ? ' AND (' . $sql_where1 . ')':'');
		} elseif ($sql_where1 != '') {
			$sql_where = 'WHERE gt.enabled="on" AND (' . $sql_where1 . ')';
		}

		$sql = "(SELECT gti.id, CONCAT('". __('Branch:') . " ', gti.title) AS name
			FROM graph_tree AS gt
			INNER JOIN graph_tree_items AS gti
			ON gti.graph_tree_id = gt.id
			AND gti.host_id = 0
			AND gti.local_graph_id=0
			$sql_join
			$sql_where
			) UNION (
			SELECT gti.id, CONCAT('" . __('Device:') . " ', h.description) AS name
			FROM graph_tree AS gt
			INNER JOIN graph_tree_items AS gti
			ON gti.graph_tree_id = gt.id
			INNER JOIN host AS h
			ON h.id=gti.host_id
			$sql_hosts_where
			$sql_join
			$sql_where
 			)
			$sql_order
			$sql_limit";

		$branches = db_fetch_assoc($sql);

		$sql = 'SELECT COUNT(*) FROM (' . $sql . ') AS rower';

		$total_rows = get_total_row_data($user_id, $sql, array(), 'branch');
	} else {
		if ($sql_where != '') {
			$sql_where = "WHERE gt.enabled='on' AND h.disabled='' AND ((h.id > 0 AND h.deleted = '') OR h.id IS NULL) AND $sql_where";
		} else {
			$sql_where = "WHERE gt.enabled='on' AND h.disabled='on' AND ((h.id > 0 AND h.deleted = '') OR h.id IS NULL)";
		}

		$sql = "(
			SELECT gti.id, CONCAT('". __('Branch:') . " ', gti.title) AS name
			FROM graph_tree AS gt
			INNER JOIN graph_tree_items AS gti
			ON gti.graph_tree_id = gt.id
			AND gti.host_id=0
			AND gti.local_graph_id=0
			$sql_where
			) UNION (
			SELECT gti.id, CONCAT('" . __('Device:') . " ', h.description) AS name
			FROM graph_tree AS gt
			INNER JOIN graph_tree_items AS gti
			ON gti.graph_tree_id = gt.id
			INNER JOIN host AS h
			ON h.id=gti.host_id
			$sql_join
			$sql_where
			)
			$sql_order
			$sql_limit";

		$branches = db_fetch_assoc($sql);

		$sql = 'SELECT COUNT(*) FROM (' . $sql . ') AS rower';

		$total_rows = get_total_row_data($user_id, $sql, array(), 'branch');
	}

	return $branches;
}

/**
 * get_allowed_devices - returns the list of devices that the user is allowed
 *   To access.  This function is generally intended for both listbox and table displays as
 *   well as other tasks.
 *
 * @param  (string) The SQL Where used to get the values or the values
 * @param  (string) The SQL Order clause to use for the sorting of devices
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (array)  An array of permitted devices
 */
function get_allowed_devices($sql_where = '', $sql_order = 'description', $sql_limit = '', &$total_rows = 0, $user_id = 0, $device_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if ($user_id == -1) {
		$auth_method = 0;
	} else {
		$auth_method = read_config_option('auth_method');
	}

	if ($auth_method > 0 && $user_id == 0) {
		if (isset($_SESSION['sess_user_id'])) {
			$user_id = $_SESSION['sess_user_id'];
		} else {
			return array();
		}
	}

	$simple_perms = get_simple_device_perms($user_id);

	$init_rows = $total_rows;

	$host_list = array();

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	if (read_user_setting('hide_disabled', false, false, $user_id) == 'on') {
		$sql_where .= ($sql_where != '' ? ' AND ':'') . '(h.disabled = "" OR h.disabled IS NULL)';
	}

	if ($sql_where != '') {
		$sql_where = "WHERE ((h.id > 0 AND h.deleted = '') OR h.id IS NULL) AND $sql_where";
	} else {
		$sql_where = "WHERE ((h.id > 0 AND h.deleted = '') OR h.id IS NULL) ";
	}

	if ($device_id > 0) {
		$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . " h.id = $device_id";
	}

	$graph_auth_method = read_config_option('graph_auth_method');

	/* get policies for all groups and user */
	$policies = get_policies($user_id);

	if (!$simple_perms && $auth_method != 0) {
		$sql_where = get_policy_where($graph_auth_method, $policies, $sql_where);
	}

	if ($total_rows != -2) {
		$host_list = db_fetch_assoc("SELECT h1.*
			FROM host AS h1
			INNER JOIN (
				SELECT DISTINCT id
				FROM (
					SELECT h.id
					FROM host AS h
					LEFT JOIN graph_local AS gl
					ON h.id=gl.host_id
					LEFT JOIN graph_templates AS gt
					ON gt.id=gl.graph_template_id
					LEFT JOIN host_template AS ht
					ON h.host_template_id=ht.id
					$sql_where
				) AS rs1
			) AS rs2
			ON rs2.id=h1.id
			$sql_order
			$sql_limit");
	}

	if ($total_rows >= 0 || $total_rows == -2) {
		$sql = "SELECT COUNT(DISTINCT id)
			FROM (
				SELECT h.id
				FROM host AS h
				LEFT JOIN graph_local AS gl
				ON h.id=gl.host_id
				LEFT JOIN graph_templates AS gt
				ON gt.id=gl.graph_template_id
				LEFT JOIN host_template AS ht
				ON h.host_template_id=ht.id
				$sql_where
			) AS rower";

		if ($device_id == 0) {
			$total_rows = get_total_row_data($user_id, $sql, array(), 'device');
		} else {
			$total_rows = db_fetch_cell($sql);
		}
	}

	return $host_list;
}

/**
 * get_allowed_sites - returns the list of sites that the user is allowed
 *   To access.  This function is generally intended for both listbox and table displays as
 *   well as other tasks.
 *
 * @param  (string) The SQL Where used to get the values or the values
 * @param  (string) The SQL Order clause to use for the sorting of devices
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 * @param  (int)    If checking a single site, specify the site_id
 *
 * @return (array)  An array of permitted sites
 */
function get_allowed_sites($sql_where = '', $sql_order = 'name', $sql_limit = '', &$total_rows = 0, $user_id = 0, $site_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	if ($sql_where != '') {
		$sql_where = "WHERE $sql_where";
	}

	if ($site_id > 0) {
		$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . " s.id=$site_id";
	}

	if ($user_id == -1) {
		$auth_method = 0;
	} else {
		$auth_method = read_config_option('auth_method');
	}

	if (isset($_SESSION['sess_user_id']) && $user_id == 0) {
		$user_id = $_SESSION['sess_user_id'];
	} elseif ($auth_method > 0) {
		return array();
	}

	$sites = db_fetch_assoc("SELECT s.id, s.name
		FROM sites AS s
		INNER JOIN host AS h
		ON s.id=h.site_id
		$sql_where
		GROUP BY s.id
		$sql_order
		$sql_limit");

	$sql = "SELECT COUNT(DISTINCT s.id)
		FROM sites AS s
		INNER JOIN host AS h
		ON s.id=h.site_id
		$sql_where";

	$total_rows = get_total_row_data($user_id, $sql, array(), 'site_device');

	return $sites;
}

/**
 * get_allowed_site_devices - returns the list of devices in a site that the user is allowed
 *   To access.  This function is generally intended for both listbox and table displays as
 *   well as other tasks.
 *
 * @param  (int)    The site id for the site
 * @param  (string) The SQL Where used to get the values or the values
 * @param  (string) The SQL Order clause to use for the sorting of devices
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (array)  An array of permitted site devices
 */
function get_allowed_site_devices($site_id, $sql_where = '', $sql_order = 'description', $sql_limit = '', &$total_rows = 0, $user_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	if ($user_id == -1) {
		$auth_method = 0;
	} else {
		$auth_method = read_config_option('auth_method');
	}

	if ($auth_method > 0 && $user_id == 0) {
		if (isset($_SESSION['sess_user_id'])) {
			$user_id = $_SESSION['sess_user_id'];
		} else {
			return array();
		}
	}

	$simple_perms = get_simple_device_perms($user_id);

	$policies = get_policies($user_id);

	$graph_auth_method = read_config_option('graph_auth_method');

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	if (read_user_setting('hide_disabled', false, false, $user_id) == 'on') {
		$sql_where .= ($sql_where != '' ? ' AND ':'') . '(h.disabled = "" OR h.disabled IS NULL)';
	}

	if ($sql_where != '') {
		$sql_where = "WHERE ((h.id > 0 AND h.deleted = '') OR h.id IS NULL) AND $sql_where";
	} else {
		$sql_where = "WHERE ((h.id > 0 AND h.deleted = '') OR h.id IS NULL) ";
	}

	if ($site_id > 0) {
		$sql_where .= ($sql_where != '' ? ' AND ' : 'WHERE ') . " h.site_id=$site_id";
	}

	if ($auth_method != 0 && !$simple_perms) {
		$sql_where = get_policy_where($graph_auth_method, $policies, $sql_where);
	}

	$host_list = db_fetch_assoc("SELECT h1.*, ht.name AS host_template_name
		FROM host AS h1
		LEFT JOIN host_template AS ht
		ON h1.host_template_id=ht.id
		INNER JOIN (
			SELECT DISTINCT id
			FROM (
				SELECT h.id
				FROM host AS h
				LEFT JOIN graph_local AS gl
				ON h.id=gl.host_id
				LEFT JOIN graph_templates_graph AS gtg
				ON gl.id=gtg.local_graph_id
				LEFT JOIN graph_templates AS gt
				ON gt.id=gl.graph_template_id
				LEFT JOIN host_template AS ht
				ON h.host_template_id=ht.id
				$sql_where
			) AS rs1
		) AS rs2
		ON rs2.id=h1.id
		$sql_order
		$sql_limit");

	$sql = "SELECT COUNT(DISTINCT id)
		FROM (
			SELECT h.id
			FROM host AS h
			LEFT JOIN graph_local AS gl
			ON h.id=gl.host_id
			LEFT JOIN graph_templates_graph AS gtg
			ON gl.id=gtg.local_graph_id
			LEFT JOIN graph_templates AS gt
			ON gt.id=gl.graph_template_id
			LEFT JOIN host_template AS ht
			ON h.host_template_id=ht.id
			$sql_where
		) AS rower";

	$total_rows = get_total_row_data($user_id, $sql, array(), 'site_device');

	return $host_list;
}

/**
 * get_allowed_graph_templates_normalized - returns the list of graph templates aligned with the
 *   To be able to differentiate between Graph Templates based on a non-data query data input mode
 *   and those related to data queries.
 *
 * @param  (string) The SQL Where used to get the values or the values
 * @param  (string) The SQL Order clause to use for the sorting of devices
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 * @param  (int)    If checking a single graph template, specify the graph_template_id
 *
 * @return (array)  An array of permitted and normalized graph templates
 */
function get_allowed_graph_templates_normalized($sql_where = '', $sql_order = 'name', $sql_limit = '', &$total_rows = 0, $user_id = 0, $graph_template_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	$templates = array_rekey(get_allowed_graph_templates($sql_where, $sql_order, $sql_limit, $total_rows, $user_id, $graph_template_id), 'id', 'name');

	if (!cacti_sizeof($templates)) {
		return array();
	}

	if ($sql_where != '') {
		$sql_where = ' WHERE (' . $sql_where . ') AND gl.graph_template_id IN(' . implode(', ', array_keys($templates)) . ') AND (gl.snmp_query_graph_id=0 OR sqg.name IS NOT NULL) AND (gt.name IS NOT NULL)';
	} else {
		$sql_where = ' WHERE gl.graph_template_id IN(' . implode(', ', array_keys($templates)) . ') AND (gl.snmp_query_graph_id=0 OR sqg.name IS NOT NULL) AND (gt.name IS NOT NULL)';
	}

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = "LIMIT $sql_limit";
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	$templates = db_fetch_assoc("SELECT
		IF(snmp_query_graph_id=0, CONCAT('cg_',gl.graph_template_id), CONCAT('dq_', gl.snmp_query_graph_id)) AS id,
		IF(snmp_query_graph_id=0, gt.name, CONCAT(gt.name, ' [', sqg.name, ']')) AS name
		FROM graph_local AS gl
		LEFT JOIN graph_templates AS gt
		ON gt.id=gl.graph_template_id
		LEFT JOIN snmp_query_graph AS sqg
		ON gl.snmp_query_graph_id=sqg.id
		AND gl.graph_template_id=sqg.graph_template_id
		$sql_where
		GROUP BY id, name
		$sql_order
		$sql_limit");

	return $templates;
}

/**
 * auth_valid_user - Returns true or false depending on if the user is valid for the system
 *   users with an id of 0 or -1 are special cases.  All non-zero users should be found in the
 *   user_auth table.
 *
 * @param  int   A valid or invalid user.
 *
 * @return bool  True is valid otherwise false
 */
function auth_valid_user($user_id) {
	static $users = array();

	// perform a check if the user exists
	if ($user_id > 0) {
		if (isset($users[$user_id]) && $users[$user_id] == true) {
			return true;
		} elseif (isset($users[$user_id])) {
			return false;
		} else {
			$exists = db_fetch_cell_prepared('SELECT id FROM user_auth WHERE id = ?', array($user_id));

			if (empty($exists)) {
				cacti_log(sprintf('ERROR: Invalid Cacti User ID %d is being used in a permission that does not exist', $user_id), false, 'AUTH');

				cacti_debug_backtrace('Invalid User Accound');

				$users[$user_id] = false;

				return false;
			}

			$users[$user_id] = true;
		}
	}

	return true;
}

/**
 * get_total_row_data - returns the total rows based upon a set of criteria
 *
 * This function will hash the $sql, and then search for the total
 * row counter based upon that criteria and if it finds a unexpired
 * match for that data, it will return the row count in the table
 * otherwise, it will execute the SQL and return the data.
 *
 * @param  (int)    The user id making the request
 * @param  (string) The sql to be executed, either prepared or otherwise
 * @param  (array)  In the case of a prepared statement the
 * @param  (string) The user defined class of data
 * @param  (int)    The timeout for the Class if not controlled by Cacti
 *
 * @return (array) an array containing a list of hosts
 */
function get_total_row_data($user_id, $sql, $sql_params = array(), $class = '', $timeout = 86400) {
	$execute  = true;
	$now_time = time();

	if (cacti_sizeof($sql_params)) {
		$nsql = str_replace('?', "'%s'", $sql);
		$nsql = vsprintf($nsql, $sql_params);

		$hash = md5($nsql);
	} else {
		$hash = md5($sql);
	}

	$row_data = db_fetch_row_prepared('SELECT total_rows, UNIX_TIMESTAMP(time) AS time
		FROM user_auth_row_cache
		WHERE user_id = ?
		AND class = ?
		AND hash = ?',
		array($user_id, $class, $hash));

	$cached = false;

	if (cacti_sizeof($row_data)) {
		$cached    = true;
		$last_time = read_config_option('time_last_change_' . $class);

		if (!empty($last_time)) {
			if ($row_data['time'] >= $last_time) {
				return $row_data['total_rows'];
			}
		} elseif ($now_time - $row_data['time'] < $timeout) {
			return $row_data['total_rows'];
		}
	}

	if (cacti_sizeof($sql_params)) {
		$rows = db_fetch_cell($sql, $sql_params);
	} else {
		$rows = db_fetch_cell($sql);
	}

	if ($user_id > 0) {
		db_execute_prepared('REPLACE INTO user_auth_row_cache
			(user_id, class, hash, total_rows, time)
			VALUES (?, ?, ?, ?, FROM_UNIXTIME(?))',
			array($user_id, $class, $hash, $rows, $now_time));
	}

	return $rows;
}

/**
 * get_host_array - returns a list of hosts taking permissions into account if necessary
 *
 * @return (array) an array containing a list of hosts
 */
function get_host_array() {
	$total_rows = -1;

	$hosts = get_allowed_devices('', 'description', '', $total_rows);

	foreach($hosts as $host) {
		$return_devices[] = strip_domain($host['description']) . ' (' . strip_domain($host['hostname']) . ')';
	}

	return $return_devices;
}

/**
 * get_allowed_ajax_hosts - returns a list of hosts in a way that can be easily read through
 *   a callback, in JSON.  The 'term' request variable will include an optional search term.
 *
 * @param  (bool)   Include the 'Any' item as the first in the list
 @ @param  (bool)   Include the 'None' item as the first or second in the list
 * @param  (string) SQL Where expression to use to gather the hosts in addition to the 'term'
 *   request variable.
 *
 * @return (string) A json array of matching devices upto a limit specified in the system
 *   settings
 */
function get_allowed_ajax_hosts($include_any = true, $include_none = true, $sql_where = '') {
	$user_id = $_SESSION['sess_user_id'];

	if (!auth_valid_user($user_id)) {
		return array();
	}

	$return = array();

	$term = get_filter_request_var('term', FILTER_CALLBACK, array('options' => 'sanitize_search_string'));
	if ($term != '') {
		$sql_where .= ($sql_where != '' ? ' AND ' : '') .
			'(hostname LIKE ' . db_qstr("%$term%") .
			' OR description LIKE ' . db_qstr("%$term%") .
			' OR notes LIKE ' . db_qstr("%$term%") . ')';
	}

	if (get_request_var('term') == '') {
		if ($include_any) {
			$return[] = array('label' => __('Any'), 'value' => 'Any', 'id' => '-1');
		}
		if ($include_none) {
			$return[] = array('label' => __('None'), 'value' => 'None', 'id' => '0');
		}
	}

	$total_rows = -1;

	$hosts = get_allowed_devices($sql_where, 'description', read_config_option('autocomplete_rows'), $total_rows);

	if (cacti_sizeof($hosts)) {
		foreach($hosts as $host) {
			$return[] = array('label' => html_escape(strip_domain($host['description'])), 'value' => html_escape($host['description']), 'id' => $host['id']);
		}
	}

	print json_encode($return);
}

/**
 * get_allowed_ajax_graph_templates - returns a list of graph_template in a way that can be easily
 *   read through a callback, in JSON.  The 'term' request variable will include an optional search term.
 *
 * @param  (bool)   Include the 'Any' item as the first in the list
 @ @param  (bool)   Include the 'None' item as the first or second in the list
 * @param  (string) SQL Where expression to use to gather the graph templates in addition to the 'term'
 *   request variable.
 *
 * @return (string) A json array of matching graph templates upto a limit specified in the system
 *   settings
 */
function get_allowed_ajax_graph_templates($include_any = true, $include_none = true, $sql_where = '') {
	$user_id = $_SESSION['sess_user_id'];

	if (!auth_valid_user($user_id)) {
		return array();
	}

	$return = array();

	$term = get_filter_request_var('term', FILTER_CALLBACK, array('options' => 'sanitize_search_string'));
	if ($term != '') {
		$sql_where .= ($sql_where != '' ? ' AND ' : '') . 'name LIKE ' . db_qstr("%$term%");
	}

	if (get_request_var('term') == '') {
		if ($include_any) {
			$return[] = array('label' => __('Any'), 'value' => 'Any', 'id' => '-1');
		}
		if ($include_none) {
			$return[] = array('label' => __('None'), 'value' => 'None', 'id' => '0');
		}
	}

	$total_rows = -1;

	$templates = get_allowed_graph_templates($sql_where, 'gt.name', read_config_option('autocomplete_rows'), $total_rows);
	if (cacti_sizeof($templates)) {
		foreach($templates as $template) {
			$return[] = array('label' => html_escape($template['name']), 'value' => html_escape($template['name']), 'id' => $template['id']);
		}
	}

	print json_encode($return);
}

/**
 * get_allowed_ajax_graph_items - returns a list of graph items in a way that can be easily
 *   read through a callback, in JSON.  The 'term' request variable will include an optional search term.
 *
 @ @param  (bool)   Include the 'None' item as the first item in the list
 * @param  (string) SQL Where expression to use to gather the hosts in addition to the 'term'
 *   request variable.
 *
 * @return (string) A json array of matching graph items upto a limit specified in the system
 *   settings
 */
function get_allowed_ajax_graph_items($include_none = true, $sql_where = '') {
	$user_id = $_SESSION['sess_user_id'];

	if (!auth_valid_user($user_id)) {
		return array();
	}

	$return = array();

	$term = get_filter_request_var('term', FILTER_CALLBACK, array('options' => 'sanitize_search_string'));
	if ($term != '') {
		$sql_where .= ($sql_where != '' ? ' AND ' : '') .
			'name_cache LIKE ' . db_qstr("%$term%") .
			' OR data_source_name LIKE ' . db_qstr("%$term%");
	}

	if (get_request_var('term') == '') {
		if ($include_none) {
			$return[] = array('label' => __('None'), 'value' => 'None', 'id' => '0');
		}
	}

	$graph_items = get_allowed_graph_items($sql_where, 'name_cache', read_config_option('autocomplete_rows'));
	if (cacti_sizeof($graph_items)) {
		foreach($graph_items as $gi) {
			$return[] = array('label' => html_escape($gi['name']), 'value' => html_escape($gi['name']), 'id' => $gi['id']);
		}
	}

	print json_encode($return);
}

/**
 * get_allowed_ajax_graph - returns a list of allowed graphs in a way that can be easily
 *   read through a callback, in JSON.  The 'term' request variable will include an optional search term.
 *
 * @param  (string) SQL Where expression to use to gather the graphs in addition to the 'term'
 *   request variable.
 *
 * @return (string) A json array of matching graphs upto a limit specified in the system
 *   settings
 */
function get_allowed_ajax_graphs($sql_where = '') {
	$user_id = $_SESSION['sess_user_id'];

	if (!auth_valid_user($user_id)) {
		return array();
	}

	$return = array();

	$term = get_filter_request_var('term', FILTER_CALLBACK, array('options' => 'sanitize_search_string'));
	if ($term != '') {
		$sql_where .= ($sql_where != '' ? ' AND ' : '') . 'title_cache LIKE ' . db_qstr("%$term%");
	}

	$total_rows = -1;

	$graphs = get_allowed_graphs($sql_where, 'gtg.title_cache', read_config_option('autocomplete_rows'), $total_rows);
	if (cacti_sizeof($graphs)) {
		foreach($graphs as $graph) {
			$return[] = array('label' => html_escape($graph['title_cache']), 'value' => html_escape($graph['title_cache']), 'id' => $graph['local_graph_id']);
		}
	}

	print json_encode($return);
}

/**
 * get_allowed_graph_items - returns a array of allowed graph items in a way that can be easily
 *   use in a table or list.
 *
 * @param  (string) The SQL Where expression to use to gather the graph items
 * @param  (string) The SQL Order clause to use for the sorting of devices
 * @param  (int)    The limit on items to return.  If empty or -1, return all items
 * @param  (int)    The number of rows found, to be returned to the caller
 * @param  (int)    If checking a user, specify the user_id otherwise for the current user leave blank
 *
 * @return (array) An array of permitted graph items
 */
function get_allowed_graph_items($sql_where, $sql_order = 'name', $sql_limit = 20, $user_id = 0) {
	if (!auth_valid_user($user_id)) {
		return array();
	}

	$return = array();

	if ($user_id == 0 && isset($_SESSION['sess_user_id'])) {
		$user_id = $_SESSION['sess_user_id'];
	}

	if ($sql_where != '') {
		$sql_where = 'WHERE ' . $sql_where;
	}

	if ($sql_limit != '' && $sql_limit != -1) {
		$sql_limit = 'LIMIT ' . $sql_limit;
	} else {
		$sql_limit = '';
	}

	if ($sql_order != '') {
		$sql_order = "ORDER BY $sql_order";
	}

	$items = db_fetch_assoc("SELECT
		CONCAT_WS('', dtd.name_cache,' (', dtr.data_source_name, ')') as name, dtr.id, dl.host_id
		FROM data_local AS dl
		INNER JOIN data_template_data AS dtd
		ON dtd.local_data_id=dl.id
		INNER JOIN data_template_rrd AS dtr
		ON dtr.local_data_id=dl.id
		LEFT JOIN host AS h
		ON dl.host_id=h.id
		$sql_where
		$sql_order
		$sql_limit");

	if (cacti_sizeof($items)) {
		foreach($items as $i) {
			if (is_device_allowed($i['host_id'], $user_id)) {
				$return[] = array('id' => $i['id'], 'name' => $i['name']);
			}
		}
	}

	return $return;
}

/**
 * auth_get_username - returns the login username for the user attempting to login
 *
 * @return (string) the username attempting to login
 */
function auth_get_username() {
	$auth_method = read_config_option('auth_method');

	if ($auth_method == 2) {
		$username = get_basic_auth_username();

		/* Get the Web Basic Auth username and set action so we login right away */
		set_request_var('action', 'login');

	} elseif (get_nfilter_request_var('action') == 'login') {
		$username = get_nfilter_request_var('login_username');
	} else {
		$username = '';
	}

	$username = sanitize_search_string($username);

	return $username;
}

/**
 * auth_checkclear_lockout - checks the lockout status of a user and unlocks if necessary
 *
 * @param  (string) $username The username of the user to check
 * @param  (int)    $realm The realm of the user to check
 *
 * @return (void)
 */
function auth_checkclear_lockout($username, $realm) {
	// Unlock the user account if timing permits
	$secPassLockFailed = read_config_option('secpass_lockfailed');
	if ($secPassLockFailed > 0) {
		$max = intval($secPassLockFailed);
		if ($max > 0) {
			$user = db_fetch_row_prepared("SELECT id, username, lastfail, failed_attempts, `locked`, password
				FROM user_auth
				WHERE username = ?
				AND realm = ?
				AND enabled = 'on'",
				array($username, $realm));

			if (cacti_sizeof($user)) {
				$unlock = intval(read_config_option('secpass_unlocktime'));
				if ($unlock > 1440) {
					$unlock = 1440;
				}

				$secs_unlock = $unlock * 60;
				$secs_fail = time() - $user['lastfail'];

				cacti_log('DEBUG: User \'' . $username . '\' secs_fail = ' . $secs_fail . ', secs_unlock = ' . $secs_unlock, false, 'AUTH', POLLER_VERBOSITY_DEBUG);

				if ($unlock > 0 && ($secs_fail > $secs_unlock)) {
					db_execute_prepared("UPDATE user_auth
						SET lastfail = 0, failed_attempts = 0, `locked` = ''
						WHERE username = ?
						AND realm = ?
						AND enabled = 'on'",
						array($username, $realm));
				}
			}
		}
	}
}

/**
 * auth_process_lockout_check - checks to see if the user is locked out of their account
 *   if there is an error, the globals error and error_msg will be set to notify the caller
 *   that a lockout is present and not to proceed with login.
 *
 * @param  (string) $username - The name of the user account
 * @param  (int)    $realm - The logging realm for the user
 *
 * @return (bool)   True if locked out, otherwise false
 */
function auth_process_lockout_check($username, $realm) {
	global $error, $error_msg;

	// Mark failed login attempts
	$secPassLockFailed = read_config_option('secpass_lockfailed');
	if ($secPassLockFailed > 0) {
		$max = intval($secPassLockFailed);
		if ($max > 0) {
			$user = db_fetch_row_prepared("SELECT id, username, lastfail, failed_attempts, `locked`, password
				FROM user_auth
				WHERE username = ?
				AND realm = ?
				AND enabled = 'on'",
				array($username, $realm));

			if (cacti_sizeof($user)) {
				if ($user['locked'] == 'on') {
					$error     = true;
					$error_msg = __('Your account has been locked.  Please contact your Administrator.');

					return true;
				}
			}
		}
	}

	return false;
}

/**
 * auth_process_lockout - called when a user login attempt fails to increment or lockout the user
 *   if there is an error, the globals error and error_msg will be set to notify the caller
 *   that a lockout is present and not to proceed with login.
 *
 * @param  (string) $username - The name of the user account
 * @param  (int)    $realm - The logging realm for the user
 *
 * @return (void)
 */
function auth_process_lockout($username, $realm) {
	global $error, $error_msg;

	// Mark failed login attempts
	$secPassLockFailed = read_config_option('secpass_lockfailed');
	if ($secPassLockFailed > 0) {
		$max = intval($secPassLockFailed);
		if ($max > 0) {
			$user = db_fetch_row_prepared("SELECT id, username, enabled, lastfail, failed_attempts, `locked`, password
				FROM user_auth
				WHERE username = ?
				AND realm = ?",
				array($username, $realm));

			if (cacti_sizeof($user)) {
				if ($user['enabled'] == '') {
					cacti_log("LOGIN FAILED: Local Login Failed for user '" . $username . "' from IP Address '" . get_client_addr() . "'.  User account Disabled.", false, 'AUTH');

					$error     = true;
					$error_msg = __('Access Denied!  Login Disabled.');
				}

				$failed = intval($user['failed_attempts']) + 1;

				cacti_log('LOGIN FAILED: User \'' . $username . '\' failed authentication, incrementing lockout (' . $failed . ' of ' . $max . ')', false, 'AUTH', POLLER_VERBOSITY_LOW);

				if ($failed >= $max) {
					db_execute_prepared("UPDATE user_auth
						SET `locked` = 'on'
						WHERE username = ?
						AND realm = ?
						AND enabled = 'on'",
						array($username, $realm));

					$user['locked'] = 'on';
				}

				$user['lastfail'] = time();

				db_execute_prepared("UPDATE user_auth
					SET lastfail = ?, failed_attempts = ?
					WHERE username = ?
					AND realm = ?
					AND enabled = 'on'",
					array($user['lastfail'], $failed, $username, $realm));

				// Log the invalid password attempt
				db_execute_prepared('INSERT IGNORE INTO user_log
					(username, user_id, result, ip, time)
					VALUES (?, ?, 0, ?, NOW())',
					array($username, isset($user['id']) ? $user['id']:0, get_client_addr()));

				if ($user['locked'] == 'on') {
					cacti_log("LOGIN FAILED: Local Login Failed for user '" . $username . "' from IP Address '" . get_client_addr() . "'.  Account is locked out.", false, 'AUTH');

					$error     = true;
					$error_msg = __('Your account has been locked.  Please contact your Administrator.');
				} else {
					cacti_log("LOGIN FAILED: Local Login Failed for user '" . $username . "' from IP Address '" . get_client_addr() . "'.", false, 'AUTH');

					/* error */
					$error     = true;
					$error_msg = __('Access Denied!  Login Failed.');
				}
			} else {
				cacti_log("LOGIN FAILED: Local Login Failed to find user '" . $username . "' from IP Address '" . get_client_addr() . "'.", false, 'AUTH');

				$error     = true;
				$error_msg = __('Access Denied!  Login Failed.');
			}
		}
	}
}

/**
 * basic_auth_login_process - login a basic auth account or generate an error
 *   if there is an error, the globals error and error_msg will be set to notify the caller
 *   that a lockout is present and not to proceed with login.  This function will also
 *   exit and return html to the display to notify the user of critical errors.
 *
 * @param  (string) $username The user to process
 *
 * @return (array|void) $user The valid user, an empty array if the user must be created
 *   or void in the case of an exit condition
 */
function basic_auth_login_process($username) {
	global $error, $error_msg;

	if (empty($username)) {
		cacti_log('ERROR: No username passed with Web Basic Authentication enabled.', false, 'AUTH');
		auth_display_custom_error_message(__('Web Basic Authentication configured, but no username was passed from the web server. Please make sure you have authentication enabled on the web server.'));

		exit;
	}

	/* Locate user in database */
	$user = db_fetch_row_prepared('SELECT *
		FROM user_auth
		WHERE username = ?
		AND realm = 2',
		array($username));

	if (!$user && get_template_account($username) == 0 && get_guest_account() === 0) {
		$error     = true;
		$error_msg = __esc('%s authenticated by Web Server, but both Template and Guest Users are not defined in Cacti.', $username);

		cacti_log("LOGIN FAILED: User '" . $username . "' authenticated by Web Server, but both Template and Guest Users are not defined in Cacti.  Exiting.", false, 'AUTH');

		auth_display_custom_error_message($error_msg);
		exit;
	}

	return $user;
}

/**
 * local_auth_login_process - login a local account or generate an error
 *   if there is an error, the globals error and error_msg will be set to notify the caller
 *   that error and not to proceed with login.
 *
 * @param  (string) $username - The user to process
 *
 * @return (array)  $user - The valid user information, or empty array if user must be created
 */
function local_auth_login_process($username) {
	$user = array();

	if (!api_plugin_hook_function('login_process', false)) {
		$user = secpass_login_process($username);

		/**
		 * If the password needs to be rehashed for security purposes,
		 * do that now.
		 */
		$stored_pass = db_fetch_cell_prepared('SELECT password
			FROM user_auth
			WHERE username = ?
			AND realm = 0',
			array($username));

		if ($stored_pass != '') {
			$password = get_nfilter_request_var('login_password');

			$valid = compat_password_verify($password, $stored_pass);

			cacti_log("DEBUG: User '" . $username . "' password for rehash is " . ($valid ? '':'in') . 'valid', false, 'AUTH', POLLER_VERBOSITY_DEBUG);

			if ($valid) {
				$user = db_fetch_row_prepared('SELECT *
					FROM user_auth
					WHERE username = ?
					AND realm = 0',
					array($username));

				if (compat_password_needs_rehash($stored_pass, PASSWORD_DEFAULT)) {
					$password = compat_password_hash($password, PASSWORD_DEFAULT);
					db_check_password_length();
					db_execute_prepared('UPDATE user_auth
						SET password = ?
						WHERE username = ?',
						array($password, $username));
				}
			}
		}
	}

	return $user;
}

/**
 * ldap_login_process - login to an LDAP account or generate an error
 *   if there is an error, the globals error and error_msg will be set to notify the caller
 *   that error and not to proceed with login.
 *
 * @param  (string) $username - The user to process
 *
 * @return (array)  $user - The valid user information, or empty array if user must be created
 */
function ldap_login_process($username) {
	global $error, $error_msg;

	$password = get_nfilter_request_var('login_password');

	if ($username == '') {
		$error     = true;
		$error_msg = __('Access Denied!  Login Failed.');

		cacti_log('LOGIN FAILED: Empty LDAP Username provided', false, 'AUTH');

		return array();
	}

	auth_checkclear_lockout($username, 3);

	if (auth_process_lockout_check($username, 3)) {
		return array();
	}

	$user  = array();
	$realm = 3;

	if ($password != '') {
		/* get user DN */
		$ldap_dn_search_response = cacti_ldap_search_dn($username);

		if ($ldap_dn_search_response['error_num'] == '0') {
			$ldap_dn = $ldap_dn_search_response['dn'];
		} else {
			/* error searching */
			$error     = true;
			$error_msg =  __('Access Denied!  LDAP Search Error: %s', $ldap_dn_search_response['error_text']);

			cacti_log('LOGIN FAILED: LDAP Error: ' . $ldap_dn_search_response['error_text'], false, 'AUTH');
		}

		if (!$error) {
			/* auth user with LDAP */
			$ldap_auth_response = cacti_ldap_auth($username, $password, $ldap_dn);

			if ($ldap_auth_response['error_num'] == '0') {
				/* Locate user in database */
				cacti_log("LOGIN: LDAP User '" . $username . "' Authenticated", false, 'AUTH');

				$user = db_fetch_row_prepared('SELECT *
					FROM user_auth
					WHERE username = ?
					AND realm = ?',
					array($username, $realm));
			} else {
				/* error */
				$error     = true;
				$error_msg = __('Access Denied!  LDAP Error: %s', $ldap_auth_response['error_text']);

				cacti_log('LOGIN FAILED: LDAP Error: ' . $ldap_auth_response['error_text'], false, 'AUTH');

				if ($ldap_auth_response['error_num'] == 1) {
					auth_process_lockout($username, $realm);
				}
			}
		}
	} else {
		/* error */
		$error     = true;
		$error_msg = __('Access Denied!  No password provided by user.');

		cacti_log(sprintf('LOGIN FAILED: LDAP No password provided for user %s', $username), false, 'AUTH');

		auth_process_lockout($username, $realm);
	}

	return $user;
}

/**
 * domains_login_process - login to an LDAP domain account or generate an error
 *   if there is an error, the globals error and error_msg will be set to notify the caller
 *   that error and not to proceed with login.
 *
 * @param  (string) $username - The user to process
 *
 * @return (array)  $user - The valid user information, or empty array if user must be created
 */
function domains_login_process($username) {
	global $realm, $error, $error_msg;

	$realm    = get_nfilter_request_var('realm');
	$password = get_nfilter_request_var('login_password');

	if ($username == '') {
		$error     = true;
		$error_msg = __('Access Denied!  Login Failed.');

		cacti_log('LOGIN FAILED: Empty Domains Username provided', false, 'AUTH');

		return array();
	}

	auth_checkclear_lockout($username, $realm);

	if (auth_process_lockout_check($username, $realm)) {
		return array();
	}

	$user = array();

	if ($realm > 3 && $password != '') {
		/* get user DN */
		$ldap_dn_search_response = domains_ldap_search_dn($username, $realm);
		if ($ldap_dn_search_response['error_num'] == '0') {
			$ldap_dn = $ldap_dn_search_response['dn'];
		} else {
			/* error searching */
			$error     = true;
			$error_msg = __('LDAP Search Error: %s', $ldap_dn_search_response['error_text']);

			cacti_log('LOGIN FAILED: LDAP Error: ' . $ldap_dn_search_response['error_text'], false, 'AUTH');
		}

		if (!$error) {
			/* auth user with LDAP */
			$ldap_auth_response = domains_ldap_auth($username, $password, $ldap_dn, $realm);

			if ($ldap_auth_response['error_num'] == '0') {
				/* User ok */
				$domain_name = db_fetch_cell_prepared('SELECT domain_name
					FROM user_domains
					WHERE domain_id = ?',
					array($realm-1000));

				/* Locate user in database */
				cacti_log("LOGIN: LDAP User '$username' Authenticated from Domain '$domain_name'", false, 'AUTH');

				$user = db_fetch_row_prepared('SELECT *
					FROM user_auth
					WHERE username = ?
					AND realm = ?',
					array($username, $realm));

				/* Create user from template if requested */
				$template_user = db_fetch_cell_prepared('SELECT user_id
					FROM user_domains
					WHERE domain_id = ?',
					array($realm-1000));

				$template_username = db_fetch_cell_prepared('SELECT username
					FROM user_auth
					WHERE id = ?',
					array($template_user));

				if (!cacti_sizeof($user) && $template_user > 0 && $username != '') {
					cacti_log("NOTE: User '" . $username . "' does not exist, copying template user", false, 'AUTH');

					/* check that template user exists */
					$user_template = db_fetch_row_prepared('SELECT *
						FROM user_auth
						WHERE id = ?',
						array($template_user));

					if (cacti_sizeof($user_template)) {
						/* template user found */
						$cn_full_name = db_fetch_cell_prepared('SELECT cn_full_name
							FROM user_domains_ldap
							WHERE domain_id = ?',
							array($realm-1000));

						$cn_email = db_fetch_cell_prepared('SELECT cn_email
							FROM user_domains_ldap
							WHERE domain_id = ?',
							array($realm-1000));

						if ($cn_full_name != '' || $cn_email != '') {
							$ldap_cn_search_response = domains_ldap_search_cn($username, array($cn_full_name, $cn_email), $realm);

							if (isset($ldap_cn_search_response['cn'])) {
								$data_override = array();

								if (array_key_exists($cn_full_name, $ldap_cn_search_response['cn'])) {
									$data_override['full_name'] = $ldap_cn_search_response['cn'][$cn_full_name];
								} else {
									$data_override['full_name'] = '';
								}

								if (array_key_exists($cn_email, $ldap_cn_search_response['cn'])) {
									$data_override['email_address'] = $ldap_cn_search_response['cn'][$cn_email];
								} else {
									$data_override['email_address'] = '';
								}

								user_copy($user_template['username'], $username, 0, $realm, false, $data_override);
							} else {
								cacti_log('LOGIN: fields not found ' . $ldap_cn_search_response[0] . 'code: ' . $ldap_cn_search_response['error_num'], false, 'AUTH');
								user_copy($user_template['username'], $username, 0, $realm);
							}
						} else {
							user_copy($user_template['username'], $username, 0, $realm);
						}

						/* requery newly created user */
						$user = db_fetch_row_prepared('SELECT *
							FROM user_auth
							WHERE username = ?
							AND realm = ?',
							array($username, $realm));
					} else {
						/* error */
						$error     = true;
						$error_msg = __('Access Denied!  Template user id %s does not exist.  Please contact your Administrator.', $template_user);

						cacti_log("LOGIN FAILED: Template user id '" . $template_user . "' does not exist.", false, 'AUTH');
					}
				}
			} else {
				/* error */
				$error     = true;
				$error_msg = __('Access Denied!  LDAP Error: %s', $ldap_auth_response['error_text']);

				cacti_log('LOGIN FAILED: LDAP Error: ' . $ldap_auth_response['error_text'], false, 'AUTH');

				if ($ldap_auth_response['error_text'] == 1) {
					auth_process_lockout($username, $realm);
				}
			}
		}
	} elseif ($password == '') {
		/* error */
		$error     = true;
		$error_msg = __('Access Denied!  No password provided by user.');

		cacti_log(sprintf('LOGIN FAILED: LDAP No password provided for user %s', $username), false, 'AUTH');

		auth_process_lockout($username, $realm);
	}

	return $user;
}

/**
 * domains_ldap_auth - authentications a LDAP domain login
 *
 * @param  (string) $username  - The user to process
 * @param  (string) $password  - The users password
 * @param  (string) $dn        - The domain name
 * @param  (int)    $realm     - The LDAP Realm number
 *
 * @return (array)  $response - The ldap response of false on a general error
 */
function domains_ldap_auth($username, $password = '', $dn = '', $realm = 0) {
	$ldap = new Ldap;

	if (!empty($username)) $ldap->username = $username;
	if (!empty($password)) $ldap->password = $password;

	$ldap->dn = $dn;

	$ld = db_fetch_row_prepared('SELECT *
		FROM user_domains_ldap
		WHERE domain_id = ?',
		array($realm-1000));

	if (cacti_sizeof($ld)) {
		if (empty($dn) && !empty($ld['dn'])) {
			$ldap->dn = $ld['dn'];
		}

		if (!empty($ld['server']))            $ldap->host              = $ld['server'];
		if (!empty($ld['port']))              $ldap->port              = $ld['port'];
		if (!empty($ld['port_ssl']))          $ldap->port_ssl          = $ld['port_ssl'];
		if (!empty($ld['proto_version']))     $ldap->version           = $ld['proto_version'];
		if (!empty($ld['encryption']))        $ldap->encryption        = $ld['encryption'];
		if (!empty($ld['referrals']))         $ldap->referrals         = $ld['referrals'];

		if (!empty($ld['mode']))              $ldap->mode              = $ld['mode'];
		if (!empty($ld['search_base']))       $ldap->search_base       = $ld['search_base'];
		if (!empty($ld['search_filter']))     $ldap->search_filter     = $ld['search_filter'];
		if (!empty($ld['specific_dn']))       $ldap->specific_dn       = $ld['specific_dn'];
		if (!empty($ld['specific_password'])) $ldap->specific_password = $ld['specific_password'];

		if ($ld['group_require'] == 'on') {
			$ldap->group_require = true;
		} else {
			$ldap->group_require = false;
		}

		if (!empty($ld['group_dn']))          $ldap->group_dn          = $ld['group_dn'];
		if (!empty($ld['group_attrib']))      $ldap->group_attrib      = $ld['group_attrib'];
		if (!empty($ld['group_member_type'])) $ldap->group_member_type = $ld['group_member_type'];

		/* If the server list is a space delimited set of servers
		 * process each server until you get a bind, or fail
		 */
		$ldap_servers = preg_split('/\s+/', $ldap->host);

		foreach($ldap_servers as $ldap_server) {
			$ldap->host = $ldap_server;

			$response = $ldap->Authenticate();

			if ($response['error_num'] == 0) {
				return $response;
			}
		}

		return $response;
	} else {
		return false;
	}
}

/**
 * domains_ldap_search_dn - searches the user dn for existence
 *
 * @param  (string) $username  - The user to process
 * @param  (int)    $realm     - The LDAP Realm number
 *
 * @return (array)  $response - The ldap response, or false on general error
 */
function domains_ldap_search_dn($username, $realm) {
	$ldap = new Ldap;

	if (!empty($username)) $ldap->username = $username;

	$ld = db_fetch_row_prepared('SELECT *
		FROM user_domains_ldap
		WHERE domain_id = ?',
		array($realm-1000));

	if (cacti_sizeof($ld)) {
		if (!empty($ld['dn']))                $ldap->dn                = $ld['dn'];
		if (!empty($ld['server']))            $ldap->host              = $ld['server'];
		if (!empty($ld['port']))              $ldap->port              = $ld['port'];
		if (!empty($ld['port_ssl']))          $ldap->port_ssl          = $ld['port_ssl'];
		if (!empty($ld['proto_version']))     $ldap->version           = $ld['proto_version'];
		if (!empty($ld['encryption']))        $ldap->encryption        = $ld['encryption'];
		if (!empty($ld['referrals']))         $ldap->referrals         = $ld['referrals'];

		if (!empty($ld['mode']))              $ldap->mode              = $ld['mode'];
		if (!empty($ld['search_base']))       $ldap->search_base       = $ld['search_base'];
		if (!empty($ld['search_filter']))     $ldap->search_filter     = $ld['search_filter'];
		if (!empty($ld['specific_dn']))       $ldap->specific_dn       = $ld['specific_dn'];
		if (!empty($ld['specific_password'])) $ldap->specific_password = $ld['specific_password'];

		if ($ld['group_require'] == 'on') {
			$ldap->group_require = true;
		} else {
			$ldap->group_require = false;
		}

		if (!empty($ld['group_dn']))          $ldap->group_dn          = $ld['group_dn'];
		if (!empty($ld['group_attrib']))      $ldap->group_attrib      = $ld['group_attrib'];
		if (!empty($ld['group_member_type'])) $ldap->group_member_type = $ld['group_member_type'];

		/* If the server list is a space delimited set of servers
		 * process each server until you get a bind, or fail
		 */
		$ldap_servers = preg_split('/\s+/', $ldap->host);

		foreach($ldap_servers as $ldap_server) {
			$ldap->host = $ldap_server;

			$response = $ldap->Search();

			if ($response['error_num'] == 0) {
				return $response;
			}
		}

		return $response;
	} else {
		return false;
	}
}

function domains_ldap_search_cn($username, $cn = array(), $realm = 0) {
	$ldap = new Ldap;

	if (!empty($username)) $ldap->username = $username;

	$ld = db_fetch_row_prepared('SELECT *
		FROM user_domains_ldap
		WHERE domain_id = ?',
		array($realm-1000));

	if (cacti_sizeof($ld)) {
		if (!empty($ld['dn']))                $ldap->dn                = $ld['dn'];
		if (!empty($ld['server']))            $ldap->host              = $ld['server'];
		if (!empty($ld['port']))              $ldap->port              = $ld['port'];
		if (!empty($ld['port_ssl']))          $ldap->port_ssl          = $ld['port_ssl'];
		if (!empty($ld['proto_version']))     $ldap->version           = $ld['proto_version'];
		if (!empty($ld['encryption']))        $ldap->encryption        = $ld['encryption'];
		if (!empty($ld['referrals']))         $ldap->referrals         = $ld['referrals'];

		if (!empty($ld['mode']))              $ldap->mode              = $ld['mode'];
		if (!empty($ld['search_base']))       $ldap->search_base       = $ld['search_base'];
		if (!empty($ld['search_filter']))     $ldap->search_filter     = $ld['search_filter'];
		if (!empty($ld['specific_dn']))       $ldap->specific_dn       = $ld['specific_dn'];
		if (!empty($ld['specific_password'])) $ldap->specific_password = $ld['specific_password'];

		$ldap->cn = $cn;

		if ($ld['group_require'] == 'on') {
			$ldap->group_require = true;
		} else {
			$ldap->group_require = false;
		}

		if (!empty($ld['group_dn']))          $ldap->group_dn          = $ld['group_dn'];
		if (!empty($ld['group_attrib']))      $ldap->group_attrib      = $ld['group_attrib'];
		if (!empty($ld['group_member_type'])) $ldap->group_member_type = $ld['group_member_type'];

		/* If the server list is a space delimited set of servers
		 * process each server until you get a bind, or fail
		 */
		$ldap_servers = preg_split('/\s+/', $ldap->host);

		foreach($ldap_servers as $ldap_server) {
			$ldap->host = $ldap_server;

			$response = $ldap->Getcn();

			if ($response['error_num'] == 0) {
				return $response;
			}
		}

		return $response;
	} else {
		return false;
	}
}

/**
 * secpass_login_process - process a local login checking for triggers
 *   such as those that would force a password check and take the appropriate action.
 *   if there is an error, the globals error and error_msg will be set to notify the caller
 *   that error and not to proceed with login.
 *
 * @param  (string) $username  - The user to process
 *
 * @return (array)  $user - The login user or an empty array if the user does not exist
 */
function secpass_login_process($username) {
	global $error, $error_msg;

	$password = get_nfilter_request_var('login_password');

	if ($username == '') {
		$error     = true;
		$error_msg = __('Access Denied!  Login Failed.');

		cacti_log('LOGIN FAILED: Empty Local Username provided', false, 'AUTH');

		return array();
	}

	auth_checkclear_lockout($username, 0);

	if (auth_process_lockout_check($username, 0)) {
		return array();
	}

	if (db_column_exists('user_auth', 'lastfail')) {
		$user = db_fetch_row_prepared("SELECT id, username, lastfail, failed_attempts, `locked`, enabled, password
			FROM user_auth
			WHERE username = ?
			AND realm = 0",
			array($username));
	} else {
		$user = db_fetch_row_prepared("SELECT id, username, password, enabled
			FROM user_auth
			WHERE username = ?
			AND realm = 0",
			array($username));
	}

	if (cacti_sizeof($user)) {
		if ($user['enabled'] != 'on') {
			$error     = true;
			$error_msg = __('Access Denied!  Login Failed.');

			cacti_log(sprintf('LOGIN FAILED: User %s, account disabled.', $username), false, 'AUTH');

			return array();
		}

		if (trim($password) == '') {
			/* error */
			$error     = true;
			$error_msg = __('Access Denied!  No password provided by user.');

			cacti_log(sprintf('LOGIN FAILED: No password provided for user %s', $username), false, 'AUTH');

			$valid_pass = false;
		} else {
			$valid_pass = compat_password_verify($password, $user['password']);
		}

		cacti_log('DEBUG: User \'' . $username . '\' valid password = ' . $valid_pass, false, 'AUTH', POLLER_VERBOSITY_DEBUG);

		if (!$valid_pass) {
			auth_process_lockout($username, 0);

			if (!$error) {
				$error     = true;
				$error_msg = __('Access Denied! Login failed.');
			}

			return array();
		}
	} else {
		/* error */
		$error     = true;
		$error_msg = __('Access Denied!  Login Failed.');

		cacti_log(sprintf('LOGIN FAILED: Invalid user %s specified.', $username), false, 'AUTH');
	}

	/**
	 * Check if old password doesn't meet specifications and must be changed
	 * This only applies to local logins where we store the actual hashed
	 * password.
	 */
	if (read_config_option('secpass_forceold') == 'on') {
		$message = secpass_check_pass($password);

		if ($message != 'ok') {
			db_execute_prepared("UPDATE user_auth
				SET must_change_password = 'on'
				WHERE username = ?
				AND realm = 0
				AND enabled = 'on'",
				array($username));

			$error_msg = __('Your Cacti administrator has forced complex passwords for logins and your current Cacti password does not match the new requirements.  Therefore, you must change your password now.');

			raise_message('forced_password', $error_msg, MESSAGE_LEVEL_INFO);
			header('Location: auth_changepassword.php?header=false');
			exit;
		}
	}

	// Set the last Login time
	if (read_config_option('secpass_expireaccount') > 0) {
		db_execute_prepared("UPDATE user_auth
			SET lastlogin = ?
			WHERE username = ?
			AND realm = 0
			AND enabled = 'on'",
			array(time(), $username));
	}

	return $user;
}

/**
 * secpass_check_pass - Validate a given password for various password rules
 *
 * @param  (string) $password - The user password
 *
 * @return (string) Either 'ok', or an error message to present to the user
 */
function secpass_check_pass($password) {
	$minlen = read_config_option('secpass_minlen');
	if (strlen($password) < $minlen) {
		return __('Password must be at least %d characters!', $minlen);
	}

	if (read_config_option('secpass_reqnum') == 'on' &&
		str_replace(array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'), '', $password) == $password
	) {
		return __('Your password must contain at least 1 numerical character!');
	}

	if (read_config_option('secpass_reqmixcase') == 'on' && strtolower($password) == $password) {
		return __('Your password must contain a mix of lower case and upper case characters!');
	}

	if (read_config_option('secpass_reqspec') == 'on' &&
		str_replace(array('~', '`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '+', '=', '[', '{', ']', '}', ';', ':', '<', ',', '.', '>', '?', '|', '/', '\\'), '', $password) == $password
	) {
		return __('Your password must contain at least 1 special character!');
	}

	return 'ok';
}

/**
 * secpass_check_history - Checks for password reuse for local accounts
 *
 * @param  (int)    $id - The user id to check
 * @param  (string) $password  - The user password
 *
 * @return (bool)   True if the user password provided meets history rules
 */
function secpass_check_history($id, $password) {
	$history = intval(read_config_option('secpass_history'));

	if ($history > 0) {
		$user = db_fetch_row_prepared("SELECT password, password_history
			FROM user_auth
			WHERE id = ?
			AND realm = 0
			AND enabled = 'on'",
			array($id));

		if (compat_password_verify($password, $user['password'])) {
			return false;
		}

		$passes = explode('|', $user['password_history']);
		// Double check this incase the password history setting was changed
		while (cacti_count($passes) > $history) {
			array_shift($passes);
		}

		if (!empty($passes)) {
			foreach ($passes as $hash) {
				if (compat_password_verify($password, $hash)) {
					return false;
				}
			}
		}
	}

	return true;
}

/**
 * rsa_check_keypair - Checks that Cacti ras_public_key is present.  If not
 *   it will insert the information into the Cacti database.
 *
 * @return (void)
 */
function rsa_check_keypair() {
	global $config;

	set_include_path($config['include_path'] . '/vendor/phpseclib/');
	include('Crypt/Base.php');
	include('Math/BigInteger.php');
	include('Crypt/Hash.php');
	include('Crypt/RSA.php');
	include('Crypt/Rijndael.php');
	include('Crypt/AES.php');

	$public_key = read_config_option('rsa_public_key');

	if(!$public_key) {
		$rsa = new phpseclib\Crypt\RSA();
		$keys = $rsa->createKey(2048);
		$rsa->loadKey($keys['publickey']);
		$fingerprint = $rsa->getPublicKeyFingerprint();

		db_execute_prepared("INSERT INTO settings
			(`name`, `value`)
			VALUES
			('rsa_public_key', '" . $keys['publickey'] . "'),
			('rsa_private_key', '" . $keys['privatekey'] . "'),
			('rsa_fingerprint', '" . $fingerprint . "')"
		);
	}
}

/**
 * reset_group_perms - sets a flag for all users of a group logged in that their perms
 *   need to be reloaded from the database
 *
 * @param  (int) $group_id - the id of the group to check
 *
 * @return (void)
 */
function reset_group_perms($group_id) {
	$users = array_rekey(db_fetch_assoc_prepared('SELECT user_id
		FROM user_auth_group_members
		WHERE group_id = ?',
		array($group_id)), 'user_id', 'user_id');

	if (cacti_sizeof($users)) {
		db_execute('UPDATE user_auth
			SET reset_perms=FLOOR(RAND() * 4294967295) + 1
			WHERE id IN (' . implode(',', $users) . ')');
	}
}

/**
 * reset_user_perms - sets a flag for all users logged in as this user that their perms
 *   need to be reloaded from the database
 *
 * @param  (int) $user_id - the id of the current user
 *
 * @return (void)
 */
function reset_user_perms($user_id) {
	db_execute_prepared('UPDATE user_auth
		SET reset_perms=FLOOR(RAND() * 4294967295) + 1
		WHERE id = ?',
		array($user_id));

	if ($user_id == $_SESSION['sess_user_id']) {
		kill_session_var('sess_user_realms');
		kill_session_var('sess_user_config_array');
		kill_session_var('sess_config_array');
		kill_session_var('sess_auth_names');
	}
}

/**
 * is_user_perms_valid - checks to see if the admin has changed users permissions
 *
 *  @param  (int)  $user_id - the id of the current user
 *
 *  @return (bool) true if still valid, false otherwise
 */
function is_user_perms_valid($user_id) {
	global $config;

	static $valid = null;
	static $key   = null;

	if ($valid === null && cacti_version_compare($config['cacti_db_version'], '1.0.0', '>=')) {
		$key = db_fetch_cell_prepared('SELECT reset_perms
			FROM user_auth
			WHERE id = ?',
			array($user_id));
	}

	if (isset($_SESSION['sess_user_perms_key'])) {
		if ($key != $_SESSION['sess_user_perms_key']) {
			$valid = false;
		} else {
			$valid = true;
		}
	} else {
		$valid = true;
	}

	$_SESSION['sess_user_perms_key'] = $key;

	return $valid;
}

/**
 * compat_password_verify - if the secure function exists, verify against that
 *   first.  If that checks fails or does not exist, check against older md5
 *   version
 *
 * @param  (string) $password - password to verify
 * @param  (string) $hash     - current password hash
 *
 * @return (bool)   true if password hash matches, false otherwise
 */
function compat_password_verify($password, $hash) {
	if (function_exists('password_verify')) {
		if (password_verify($password, $hash)) {
			return true;
		}
	}

	$md5 = md5($password);

	return ($md5 === $hash);
}

/**
 * compat_password_hash - if the secure function exists, hash using that.
 *   If that does not exist, hash older md5 function instead
 *
 * @param  (string) $password - password to hash
 * @param  (string) $algo     - algorithm to use (PASSWORD_DEFAULT)
 *
 * @return (bool)   true if password hash matches, false otherwise
 */
function compat_password_hash($password, $algo, $options = array()) {
	if (function_exists('password_hash')) {
		// Check if options array has anything, only pass when required
		return (cacti_sizeof($options) > 0) ?
			password_hash($password, $algo, $options) :
			password_hash($password, $algo);
	}

	return md5($password);
}

/**
 * compat_password_needs_rehash - if the secure function exists, check hash
 *   using that. If that does not exist, return false as md5 doesn't need a
 *   rehash
 *
 * @param  (string) $password - password to hash
 * @param  (string) $algo     - algorithm to use (PASSWORD_DEFAULT)
 *
 * @return (bool)   true if password hash needs changing, false otherwise
 */
function compat_password_needs_rehash($password, $algo, $options = array()) {
	if (function_exists('password_needs_rehash')) {
		// Check if options array has anything, only pass when required
		return (cacti_sizeof($options) > 0) ?
			password_needs_rehash($password, $algo, $options) :
			password_needs_rehash($password, $algo);
	}

	return true;
}

/**
 * auth_user_has_access - Verify that the user account has some access to cacti
 *
 * @param  (int)  $user - The user id of the account to check
 *
 * @return (bool) True if the user has access false otherwise
 */
function auth_user_has_access($user) {
	$access = false;

	// See if they have access to any realms
	$realms = db_fetch_cell_prepared('SELECT COUNT(*)
		FROM user_auth_realm
		WHERE user_id = ?',
		array($user['id']));

	if ($realms > 0) {
		return true;
	}

	// See if they have general graph access as a guest account
	if (read_config_option('guest_user') > 0) {
		if ($user['show_tree'] == 'on' || $user['show_list'] == 'on' || $user['show_preview'] == 'on') {
			return true;
		}
	}

	// See if they have access to any group realms
	$user_groups = db_fetch_assoc_prepared('SELECT *
		FROM user_auth_group_members
		WHERE user_id = ?',
		array($user['id']));

	if (cacti_sizeof($user_groups)) {
		foreach($user_groups as $g) {
			$realms = db_fetch_cell_prepared('SELECT COUNT(*)
				FROM user_auth_group_realm
				WHERE group_id = ?',
				array($g['group_id']));

			if ($realms > 0) {
				return true;
			}

			// See if they have general graph access as a guest account
			if (read_config_option('guest_user') > 0) {
				if ($g['show_tree'] == 'on' || $g['show_list'] == 'on' || $g['show_preview'] == 'on') {
					return true;
				}
			}
		}
	}

	return false;
}

/**
 * auth_display_custom_error_message - displays a custom error message to the browser that looks like
 *   the pre-defined error messages
 *
 * @param  (string) $message - the actual text of the error message to display
 *
 * @return (void)
 */
function auth_display_custom_error_message($message) {
	global $config;

	$auth_method = read_config_option('auth_method');

	if ($auth_method == 2) {
		$custom_message = read_config_option('basic_auth_fail_message');
	} else {
		$custom_message = '';
	}

	/* kill the session */
	cacti_cookie_logout();

	/* print error */
	print '<!DOCTYPE html>';
	print '<html>';
	print '<head>';

	html_common_header(__('Cacti Login Failure'));

	print '</head>';
	print '<body><center>';
	print '<div class="ui-state-error ui-corner-all" style="width:50%;margin-left:auto;margin-right:auto;margin-top:200px;padding:20px"><p>' . $message . '</p><p>' . $custom_message . '</p></div>';

	if ($auth_method != 2) {
		print '<div class="ui-corner-all" style="width:50%;margin:auto;padding:20px"><a href="index.php">' . __('Login Again') . '</a></div><script type="text/javascript">$(function() { $("a").button(); });</script>';
	}

	print '</center></body></html>';
}

/**
 * auth_login_redirect - provide default page re-direction when a user first logs in.
 *
 * @param  (string|array) $login_opts - optional array of user details
 *
 * @return (void)
 */
function auth_login_redirect($login_opts = '') {
	global $config;

	if ($login_opts == '') {
		$login_opts = db_fetch_cell_prepared('SELECT login_opts
			FROM user_auth
			WHERE id = ?',
			array($_SESSION['sess_user_id']));
	}

	$newtheme = false;
	if (user_setting_exists('selected_theme', $_SESSION['sess_user_id']) && read_config_option('selected_theme') != read_user_setting('selected_theme')) {
		unset($_SESSION['selected_theme']);
		$newtheme = true;
	}

	// Decide what to do with an authenticated user
	switch ($login_opts) {
		case '1': /* referer */
			/**
			 * because we use plugins, we can't redirect back to graph_view.php if they don't
			 * have console access
			 */
			if (isset($_SERVER['REDIRECT_URL'])) {
				$referer = sanitize_uri($_SERVER['REDIRECT_URL']);

				if (isset($_SERVER['REDIRECT_QUERY_STRING'])) {
					$referer .= '?' . $_SERVER['REDIRECT_QUERY_STRING'];
				}

				cacti_log(sprintf("DEBUG: Referer from REDIRECT_URL with Value: '%s', Effective: '%s'", $_SERVER['REDIRECT_URL'], $referer), false, 'AUTH', POLLER_VERBOSITY_DEBUG);
			} elseif (isset($_SERVER['HTTP_REFERER'])) {
				$referer = $_SERVER['HTTP_REFERER'];

				if (auth_basename($referer) == 'logout.php') {
					$referer = $config['url_path'] . 'index.php';
				} elseif (strpos($referer, $config['url_path']) === false) {
					if (!is_realm_allowed(8)) {
						$referer = $config['url_path'] . 'graph_view.php' . ($newtheme ? '?newtheme=1':'');
					} else {
						$referer = $config['url_path'] . 'index.php' . ($newtheme ? '?newtheme=1':'');
					}
				}

				cacti_log(sprintf("DEBUG: Referer from HTTP_REFERER with Value: '%s', Effective: '%s'", $_SERVER['HTTP_REFERER'], $referer), false, 'AUTH', POLLER_VERBOSITY_DEBUG);
			} elseif (isset($_SERVER['REQUEST_URI'])) {
				$referer = sanitize_uri($_SERVER['REQUEST_URI']);

				if (auth_basename($referer) == 'logout.php') {
					$referer = $config['url_path'] . 'index.php';
				}

				cacti_log(sprintf("DEBUG: Referer from REQUEST_URI with Value: '%s', Effective: '%s'", $_SERVER['REQUEST_URI'], $referer), false, 'AUTH', POLLER_VERBOSITY_DEBUG);
			} else {
				$referer = $config['url_path'] . 'index.php';

				cacti_log(sprintf("DEBUG: Referer Short Circuit to '%s'", 'index.php'), false, 'AUTH', POLLER_VERBOSITY_DEBUG);
			}

			$referer .= ($newtheme ? (strpos($referer, '?') === false ? '?':'&') . 'newtheme=1':'');

			/* Strip out the login from the referer if present */
			$referer  = str_replace('?action=login', '', $referer);

			if (api_user_realm_auth(auth_basename($referer))) {
				header('Location: ' . $referer);
			} elseif (!is_realm_allowed(8)) {
				cacti_log(sprintf("DEBUG: Referer Overridden Due to Permissions to '%s'", 'graph_view.php'), false, 'AUTH', POLLER_VERBOSITY_DEBUG);

				header('Location: graph_view.php');
			} else {
				cacti_log(sprintf("DEBUG: Referer Overridden Due to Permissions to '%s'", 'index.php'), false, 'AUTH', POLLER_VERBOSITY_DEBUG);

				header('Location: index.php');
			}

			break;
		case '2': /* default console page */
			if (!is_realm_allowed(8)) {
				header('Location: ' . $config['url_path'] . 'graph_view.php' . ($newtheme ? '?newtheme=1':''));
			} else {
				header('Location: ' . $config['url_path'] . 'index.php' . ($newtheme ? '?newtheme=1':''));
			}

			break;
		case '3': /* default graph page */
			header('Location: ' . $config['url_path'] . 'graph_view.php' . ($newtheme ? '?newtheme=1':''));

			break;
		default:
			api_plugin_hook_function('login_options_navigate', $login_opts);
	}

	exit;
}

/**
 * auth_basename - provides a URL knowledgable basename function
 *
 * @param  (string) $referer - a URL that will included a basename
 *
 * @return (string) the file name without the arguments
 */
function auth_basename($referer) {
	$parts = explode('?', $referer);

	return basename($parts[0]);
}

/**
 * auth_login_create_user_from_template - creates a new user account from a template account
 *   if there is an error that would block login, the function set's the globals
 *   error and error_msg to inform the caller not to proceed with the login.
 *   in special cases, such as basic auth, the function will print out a custom
 *   error message and exit.
 *
 * @param  (string) $username - The username to use for the copy
 * @param  (int)    $realm - The login realm to use for the copy
 *
 * @return (array|void)  The copied new user account details or void on exit
 */
function auth_login_create_user_from_template($username, $realm) {
	global $error, $error_msg;

	cacti_log("NOTE: User '" . $username . "' does not exist, copying template user", false, 'AUTH');

	$user = array();

	$user_template = db_fetch_row_prepared('SELECT *
		FROM user_auth
		WHERE id = ?',
		array(get_template_account($username)));

	/* check that template user exists */
	if (!empty($user_template)) {
		if ($realm == 3) { // This is an ldap login
			/* get user CN*/
			$cn_full_name = read_config_option('cn_full_name');
			$cn_email     = read_config_option('cn_email');

			if ($cn_full_name != '' || $cn_email != '') {
				$ldap_cn_search_response = cacti_ldap_search_cn($username, array($cn_full_name, $cn_email));

				if (isset($ldap_cn_search_response['cn'])) {
					$data_override = array();

					if (array_key_exists($cn_full_name, $ldap_cn_search_response['cn'])) {
						$data_override['full_name'] = $ldap_cn_search_response['cn'][$cn_full_name];
					} else {
						$data_override['full_name'] = '';
					}

					if (array_key_exists($cn_email, $ldap_cn_search_response['cn'])) {
						$data_override['email_address'] = $ldap_cn_search_response['cn'][$cn_email];
					} else {
						$data_override['email_address'] = '';
					}

					user_copy($user_template['username'], $username, $user_template['realm'], $realm, false, $data_override);
				} else {
					$ldap_response = (isset($ldap_cn_search_response[0]) ? $ldap_cn_search_response[0] : '(no response given)');
					$ldap_code = (isset($ldap_cn_search_response['error_num']) ? $ldap_cn_search_response['error_num'] : '(no code given)');
					cacti_log('LOGIN: Email Address and Full Name fields not found, reason: ' . $ldap_response . 'code: ' . $ldap_code, false, 'AUTH');
					user_copy($user_template['username'], $username, $user_template['realm'], $realm);
				}
			} else {
				user_copy($user_template['username'], $username, $user_template['realm'], $realm);
			}
		} else {
			user_copy($user_template['username'], $username, $user_template['realm'], $realm);
		}

		/* requery newly created user */
		$user = db_fetch_row_prepared('SELECT *
			FROM user_auth
			WHERE username = ?
			AND realm = ?',
			array($username, $realm));
	} else {
		/* error */
		$error     = true;
		$error_msg = __('Access Denied!  Template user id %s does not exist.  Please contact your Administrator.', read_config_option('user_template'));

		cacti_log("LOGIN FAILED: Template user id '" . read_config_option('user_template') . "' does not exist.", false, 'AUTH');

		if ($auth_method == 2) {
			auth_display_custom_error_message($error_msg);
			exit;
		}
	}

	return $user;
}

/**
 * check_reset_no_authentication - Attempts to switch Cacti from No Authentication to Local
 *   authentication, or generate an error on failure through the globals error, and error_msg.
 *
 * @param  (int)  $auth_method - The current auth method
 *
 * @return (bool) Returns false on failure to set user account, otherwise redirects
 */
function check_reset_no_authentication($auth_method) {
	global $config, $error, $error_msg;

	if ($auth_method == 0) {
		$admin_id = db_execute_prepared('SELECT id
			FROM user_auth
			WHERE id = ?',
			array(read_config_option('admin_user')));

		cacti_log('Admin User (' . read_config_option('admin_user') . ' vs ' . $admin_id . ')', true, 'AUTH_NONE', POLLER_VERBOSITY_DEVDBG);

		if (!$admin_id) {
			$admin_sql_query = 'SELECT TOP 1 id FROM (
				SELECT ua.id
				FROM user_auth AS ua
				INNER JOIN user_auth_realm AS uar
				ON uar.user_id = ua.id
				WHERE uar.realm_id = ?';

			$admin_sql_params = array(15);

			if (db_table_exists('user_auth_group_realm')) {
				$admin_sql_query .= '
				UNION
				SELECT ua.id
				FROM user_auth AS ua
				INNER JOIN user_auth_group_members AS uagm
				ON uagm.user_id = ua.id
				INNER JOIN user_auth_group AS uag
				ON uag.id = uagm.group_id
				INNER JOIN user_auth_group_realm AS uagr
				ON uagr.group_id=uag.group_id
				WHERE uag.enabled="on" AND ua.enabled="on"
				AND uagr.realm_id = ?';

				$admin_sql_params[] = 15;
			}

			$admin_sql_query .= '
				) AS id';

			cacti_log('SQL query ' . $admin_sql_query, true, 'AUTH_NONE', POLLER_VERBOSITY_DEVDBG);
			cacti_log('SQL param ' . implode(',', $admin_sql_params), true, 'AUTH_NONE', POLLER_VERBOSITY_DEVDBG);
			$admin_id = db_fetch_cell_prepared($admin_sql_query, $admin_sql_params);
			cacti_log('SQL result ' . $admin_id, true, 'AUTH_NONE', POLLER_VERBOSITY_DEVDBG);
		}

		if (!$admin_id) {
			$admin_id = db_fetch_cell('SELECT id FROM user_auth WHERE username = \'admin\'');
			cacti_log('Final attempt ' . $admin_id, true, 'AUTH_NONE', POLLER_VERBOSITY_DEVDBG);
		}

		if (!$admin_id) {
			$error     = true;
			$error_msg = __('Authentication was previously not set.  Attempted to set to Local Authentication, but no Administrative account was found.');

			return false;
		}

		// Authentication method is currently set to none
		// lets switch this to basic and allow setting of
		// a password.
		db_execute_prepared("UPDATE user_auth SET
			password = '',
			must_change_password = 'on',
			password_change = 'on'
			WHERE id = ?",
			array($admin_id));

		$auth_method = 1;
		set_config_option('auth_method', $auth_method, true);

		$_SESSION['sess_user_id'] = $admin_id;
		$_SESSION['sess_change_password'] = true;
		header ('Location: ' . $config['url_path'] . 'auth_changepassword.php?action=force&ref=' . (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : 'index.php'));
		exit;
	}
}

