PageRenderTime 143ms CodeModel.GetById 92ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 1ms

/sources/Security.php

https://github.com/Arantor/Elkarte
PHP | 1437 lines | 920 code | 173 blank | 344 comment | 245 complexity | edbe7f6e17fe9a09fcea69bfb8e6270d MD5 | raw file
   1<?php
   2
   3/**
   4 * @name      ElkArte Forum
   5 * @copyright ElkArte Forum contributors
   6 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
   7 *
   8 * This software is a derived product, based on:
   9 *
  10 * Simple Machines Forum (SMF)
  11 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
  12 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
  13 *
  14 * @version 1.0 Alpha
  15 *
  16 * This file has the very important job of ensuring forum security.
  17 * This task includes banning and permissions, namely.
  18 *
  19 */
  20
  21if (!defined('ELKARTE'))
  22	die('No access...');
  23
  24/**
  25 * Check if the user is who he/she says he is
  26 * Makes sure the user is who they claim to be by requiring a password to be typed in every hour.
  27 * Is turned on and off by the securityDisable setting.
  28 * Uses the adminLogin() function of subs/Auth.subs.php if they need to login, which saves all request (post and get) data.
  29 *
  30 * @param string $type = admin
  31 */
  32function validateSession($type = 'admin')
  33{
  34	global $modSettings, $user_info, $sc, $user_settings;
  35
  36	// We don't care if the option is off, because Guests should NEVER get past here.
  37	is_not_guest();
  38
  39	// Validate what type of session check this is.
  40	$types = array();
  41	call_integration_hook('integrate_validateSession', array($types));
  42	$type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin';
  43
  44	// If we're using XML give an additional ten minutes grace as an admin can't log on in XML mode.
  45	$refreshTime = isset($_GET['xml']) ? 4200 : 3600;
  46
  47	// Is the security option off?
  48	if (!empty($modSettings['securityDisable' . ($type != 'admin' ? '_' . $type : '')]))
  49		return;
  50
  51	// Or are they already logged in?, Moderator or admin sesssion is need for this area
  52	if ((!empty($_SESSION[$type . '_time']) && $_SESSION[$type . '_time'] + $refreshTime >= time()) || (!empty($_SESSION['admin_time']) && $_SESSION['admin_time'] + $refreshTime >= time()))
  53		return;
  54
  55	require_once(SUBSDIR . '/Auth.subs.php');
  56
  57	// Hashed password, ahoy!
  58	if (isset($_POST[$type . '_hash_pass']) && strlen($_POST[$type . '_hash_pass']) == 40)
  59	{
  60		checkSession();
  61
  62		$good_password = in_array(true, call_integration_hook('integrate_verify_password', array($user_info['username'], $_POST[$type . '_hash_pass'], true)), true);
  63
  64		if ($good_password || $_POST[$type . '_hash_pass'] == sha1($user_info['passwd'] . $sc))
  65		{
  66			$_SESSION[$type . '_time'] = time();
  67			unset($_SESSION['request_referer']);
  68			return;
  69		}
  70	}
  71	// Posting the password... check it.
  72	if (isset($_POST[$type. '_pass']))
  73	{
  74		checkSession();
  75
  76		$good_password = in_array(true, call_integration_hook('integrate_verify_password', array($user_info['username'], $_POST[$type . '_pass'], false)), true);
  77
  78		// Password correct?
  79		if ($good_password || sha1(strtolower($user_info['username']) . $_POST[$type . '_pass']) == $user_info['passwd'])
  80		{
  81			$_SESSION[$type . '_time'] = time();
  82			unset($_SESSION['request_referer']);
  83			return;
  84		}
  85	}
  86
  87	// OpenID?
  88	if (!empty($user_settings['openid_uri']))
  89	{
  90		require_once(SUBSDIR . '/OpenID.subs.php');
  91		openID_revalidate();
  92
  93		$_SESSION[$type . '_time'] = time();
  94		unset($_SESSION['request_referer']);
  95		return;
  96	}
  97
  98	// Better be sure to remember the real referer
  99	if (empty($_SESSION['request_referer']))
 100		$_SESSION['request_referer'] = isset($_SERVER['HTTP_REFERER']) ? @parse_url($_SERVER['HTTP_REFERER']) : array();
 101	elseif (empty($_POST))
 102		unset($_SESSION['request_referer']);
 103
 104	// Need to type in a password for that, man.
 105	if (!isset($_GET['xml']))
 106		adminLogin($type);
 107	else
 108		return 'session_verify_fail';
 109}
 110
 111/**
 112 * Require a user who is logged in. (not a guest.)
 113 * Checks if the user is currently a guest, and if so asks them to login with a message telling them why.
 114 * Message is what to tell them when asking them to login.
 115 *
 116 * @param string $message = ''
 117 */
 118function is_not_guest($message = '')
 119{
 120	global $user_info, $txt, $context, $scripturl;
 121
 122	// Luckily, this person isn't a guest.
 123	if (isset($user_info['is_guest']) && !$user_info['is_guest'])
 124		return;
 125
 126	// People always worry when they see people doing things they aren't actually doing...
 127	$_GET['action'] = '';
 128	$_GET['board'] = '';
 129	$_GET['topic'] = '';
 130	writeLog(true);
 131
 132	// Just die.
 133	if (isset($_REQUEST['xml']))
 134		obExit(false);
 135
 136	// Attempt to detect if they came from dlattach.
 137	if (ELKARTE != 'SSI' && empty($context['theme_loaded']))
 138		loadTheme();
 139
 140	// Never redirect to an attachment
 141	if (strpos($_SERVER['REQUEST_URL'], 'dlattach') === false)
 142		$_SESSION['login_url'] = $_SERVER['REQUEST_URL'];
 143
 144	// Load the Login template and language file.
 145	loadLanguage('Login');
 146
 147	// Apparently we're not in a position to handle this now. Let's go to a safer location for now.
 148	if (empty($context['template_layers']))
 149	{
 150		$_SESSION['login_url'] = $scripturl . '?' . $_SERVER['QUERY_STRING'];
 151		redirectexit('action=login');
 152	}
 153	else
 154	{
 155		loadTemplate('Login');
 156		$context['sub_template'] = 'kick_guest';
 157		$context['robot_no_index'] = true;
 158	}
 159
 160	// Use the kick_guest sub template...
 161	$context['kick_message'] = $message;
 162	$context['page_title'] = $txt['login'];
 163
 164	obExit();
 165
 166	// We should never get to this point, but if we did we wouldn't know the user isn't a guest.
 167	trigger_error('Hacking attempt...', E_USER_ERROR);
 168}
 169
 170/**
 171 * Do banning related stuff.  (ie. disallow access....)
 172 * Checks if the user is banned, and if so dies with an error.
 173 * Caches this information for optimization purposes.
 174 * Forces a recheck if force_check is true.
 175 *
 176 * @param bool $forceCheck = false
 177 */
 178function is_not_banned($forceCheck = false)
 179{
 180	global $txt, $modSettings, $context, $user_info;
 181	global $cookiename, $user_settings, $smcFunc;
 182
 183	// You cannot be banned if you are an admin - doesn't help if you log out.
 184	if ($user_info['is_admin'])
 185		return;
 186
 187	// Only check the ban every so often. (to reduce load.)
 188	if ($forceCheck || !isset($_SESSION['ban']) || empty($modSettings['banLastUpdated']) || ($_SESSION['ban']['last_checked'] < $modSettings['banLastUpdated']) || $_SESSION['ban']['id_member'] != $user_info['id'] || $_SESSION['ban']['ip'] != $user_info['ip'] || $_SESSION['ban']['ip2'] != $user_info['ip2'] || (isset($user_info['email'], $_SESSION['ban']['email']) && $_SESSION['ban']['email'] != $user_info['email']))
 189	{
 190		// Innocent until proven guilty.  (but we know you are! :P)
 191		$_SESSION['ban'] = array(
 192			'last_checked' => time(),
 193			'id_member' => $user_info['id'],
 194			'ip' => $user_info['ip'],
 195			'ip2' => $user_info['ip2'],
 196			'email' => $user_info['email'],
 197		);
 198
 199		$ban_query = array();
 200		$ban_query_vars = array('current_time' => time());
 201		$flag_is_activated = false;
 202
 203		// Check both IP addresses.
 204		foreach (array('ip', 'ip2') as $ip_number)
 205		{
 206			if ($ip_number == 'ip2' && $user_info['ip2'] == $user_info['ip'])
 207				continue;
 208			$ban_query[] = constructBanQueryIP($user_info[$ip_number]);
 209			// IP was valid, maybe there's also a hostname...
 210			if (empty($modSettings['disableHostnameLookup']) && $user_info[$ip_number] != 'unknown')
 211			{
 212				$hostname = host_from_ip($user_info[$ip_number]);
 213				if (strlen($hostname) > 0)
 214				{
 215					$ban_query[] = '({string:hostname} LIKE bi.hostname)';
 216					$ban_query_vars['hostname'] = $hostname;
 217				}
 218			}
 219		}
 220
 221		// Is their email address banned?
 222		if (strlen($user_info['email']) != 0)
 223		{
 224			$ban_query[] = '({string:email} LIKE bi.email_address)';
 225			$ban_query_vars['email'] = $user_info['email'];
 226		}
 227
 228		// How about this user?
 229		if (!$user_info['is_guest'] && !empty($user_info['id']))
 230		{
 231			$ban_query[] = 'bi.id_member = {int:id_member}';
 232			$ban_query_vars['id_member'] = $user_info['id'];
 233		}
 234
 235		// Check the ban, if there's information.
 236		if (!empty($ban_query))
 237		{
 238			$restrictions = array(
 239				'cannot_access',
 240				'cannot_login',
 241				'cannot_post',
 242				'cannot_register',
 243			);
 244			$request = $smcFunc['db_query']('', '
 245				SELECT bi.id_ban, bi.email_address, bi.id_member, bg.cannot_access, bg.cannot_register,
 246					bg.cannot_post, bg.cannot_login, bg.reason, IFNULL(bg.expire_time, 0) AS expire_time
 247				FROM {db_prefix}ban_items AS bi
 248					INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time}))
 249				WHERE
 250					(' . implode(' OR ', $ban_query) . ')',
 251				$ban_query_vars
 252			);
 253			// Store every type of ban that applies to you in your session.
 254			while ($row = $smcFunc['db_fetch_assoc']($request))
 255			{
 256				foreach ($restrictions as $restriction)
 257					if (!empty($row[$restriction]))
 258					{
 259						$_SESSION['ban'][$restriction]['reason'] = $row['reason'];
 260						$_SESSION['ban'][$restriction]['ids'][] = $row['id_ban'];
 261						if (!isset($_SESSION['ban']['expire_time']) || ($_SESSION['ban']['expire_time'] != 0 && ($row['expire_time'] == 0 || $row['expire_time'] > $_SESSION['ban']['expire_time'])))
 262							$_SESSION['ban']['expire_time'] = $row['expire_time'];
 263
 264						if (!$user_info['is_guest'] && $restriction == 'cannot_access' && ($row['id_member'] == $user_info['id'] || $row['email_address'] == $user_info['email']))
 265							$flag_is_activated = true;
 266					}
 267			}
 268			$smcFunc['db_free_result']($request);
 269		}
 270
 271		// Mark the cannot_access and cannot_post bans as being 'hit'.
 272		if (isset($_SESSION['ban']['cannot_access']) || isset($_SESSION['ban']['cannot_post']) || isset($_SESSION['ban']['cannot_login']))
 273			log_ban(array_merge(isset($_SESSION['ban']['cannot_access']) ? $_SESSION['ban']['cannot_access']['ids'] : array(), isset($_SESSION['ban']['cannot_post']) ? $_SESSION['ban']['cannot_post']['ids'] : array(), isset($_SESSION['ban']['cannot_login']) ? $_SESSION['ban']['cannot_login']['ids'] : array()));
 274
 275		// If for whatever reason the is_activated flag seems wrong, do a little work to clear it up.
 276		if ($user_info['id'] && (($user_settings['is_activated'] >= 10 && !$flag_is_activated)
 277			|| ($user_settings['is_activated'] < 10 && $flag_is_activated)))
 278		{
 279			require_once(ADMINDIR . '/ManageBans.php');
 280			updateBanMembers();
 281		}
 282	}
 283
 284	// Hey, I know you! You're ehm...
 285	if (!isset($_SESSION['ban']['cannot_access']) && !empty($_COOKIE[$cookiename . '_']))
 286	{
 287		$bans = explode(',', $_COOKIE[$cookiename . '_']);
 288		foreach ($bans as $key => $value)
 289			$bans[$key] = (int) $value;
 290		$request = $smcFunc['db_query']('', '
 291			SELECT bi.id_ban, bg.reason
 292			FROM {db_prefix}ban_items AS bi
 293				INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)
 294			WHERE bi.id_ban IN ({array_int:ban_list})
 295				AND (bg.expire_time IS NULL OR bg.expire_time > {int:current_time})
 296				AND bg.cannot_access = {int:cannot_access}
 297			LIMIT ' . count($bans),
 298			array(
 299				'cannot_access' => 1,
 300				'ban_list' => $bans,
 301				'current_time' => time(),
 302			)
 303		);
 304		while ($row = $smcFunc['db_fetch_assoc']($request))
 305		{
 306			$_SESSION['ban']['cannot_access']['ids'][] = $row['id_ban'];
 307			$_SESSION['ban']['cannot_access']['reason'] = $row['reason'];
 308		}
 309		$smcFunc['db_free_result']($request);
 310
 311		// My mistake. Next time better.
 312		if (!isset($_SESSION['ban']['cannot_access']))
 313		{
 314			require_once(SUBSDIR . '/Auth.subs.php');
 315			$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
 316			elk_setcookie($cookiename . '_', '', time() - 3600, $cookie_url[1], $cookie_url[0], false, false);
 317		}
 318	}
 319
 320	// If you're fully banned, it's end of the story for you.
 321	if (isset($_SESSION['ban']['cannot_access']))
 322	{
 323		// We don't wanna see you!
 324		if (!$user_info['is_guest'])
 325			$smcFunc['db_query']('', '
 326				DELETE FROM {db_prefix}log_online
 327				WHERE id_member = {int:current_member}',
 328				array(
 329					'current_member' => $user_info['id'],
 330				)
 331			);
 332
 333		// 'Log' the user out.  Can't have any funny business... (save the name!)
 334		$old_name = isset($user_info['name']) && $user_info['name'] != '' ? $user_info['name'] : $txt['guest_title'];
 335		$user_info['name'] = '';
 336		$user_info['username'] = '';
 337		$user_info['is_guest'] = true;
 338		$user_info['is_admin'] = false;
 339		$user_info['permissions'] = array();
 340		$user_info['id'] = 0;
 341		$context['user'] = array(
 342			'id' => 0,
 343			'username' => '',
 344			'name' => $txt['guest_title'],
 345			'is_guest' => true,
 346			'is_logged' => false,
 347			'is_admin' => false,
 348			'is_mod' => false,
 349			'can_mod' => false,
 350			'language' => $user_info['language'],
 351		);
 352
 353		// A goodbye present.
 354		require_once(SUBSDIR . '/Auth.subs.php');
 355		$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
 356		elk_setcookie($cookiename . '_', implode(',', $_SESSION['ban']['cannot_access']['ids']), time() + 3153600, $cookie_url[1], $cookie_url[0], false, false);
 357
 358		// Don't scare anyone, now.
 359		$_GET['action'] = '';
 360		$_GET['board'] = '';
 361		$_GET['topic'] = '';
 362		writeLog(true);
 363
 364		// You banned, sucka!
 365		fatal_error(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_access']['reason']) ? '' : '<br />' . $_SESSION['ban']['cannot_access']['reason']) . '<br />' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']), 'user');
 366
 367		// If we get here, something's gone wrong.... but let's try anyway.
 368		trigger_error('Hacking attempt...', E_USER_ERROR);
 369	}
 370	// You're not allowed to log in but yet you are. Let's fix that.
 371	elseif (isset($_SESSION['ban']['cannot_login']) && !$user_info['is_guest'])
 372	{
 373		// We don't wanna see you!
 374		$smcFunc['db_query']('', '
 375			DELETE FROM {db_prefix}log_online
 376			WHERE id_member = {int:current_member}',
 377			array(
 378				'current_member' => $user_info['id'],
 379			)
 380		);
 381
 382		// 'Log' the user out.  Can't have any funny business... (save the name!)
 383		$old_name = isset($user_info['name']) && $user_info['name'] != '' ? $user_info['name'] : $txt['guest_title'];
 384		$user_info['name'] = '';
 385		$user_info['username'] = '';
 386		$user_info['is_guest'] = true;
 387		$user_info['is_admin'] = false;
 388		$user_info['permissions'] = array();
 389		$user_info['id'] = 0;
 390		$context['user'] = array(
 391			'id' => 0,
 392			'username' => '',
 393			'name' => $txt['guest_title'],
 394			'is_guest' => true,
 395			'is_logged' => false,
 396			'is_admin' => false,
 397			'is_mod' => false,
 398			'can_mod' => false,
 399			'language' => $user_info['language'],
 400		);
 401
 402		// Wipe 'n Clean(r) erases all traces.
 403		$_GET['action'] = '';
 404		$_GET['board'] = '';
 405		$_GET['topic'] = '';
 406		writeLog(true);
 407
 408		require_once(CONTROLLERDIR . '/LogInOut.controller.php');
 409		Logout(true, false);
 410
 411		fatal_error(sprintf($txt['your_ban'], $old_name) . (empty($_SESSION['ban']['cannot_login']['reason']) ? '' : '<br />' . $_SESSION['ban']['cannot_login']['reason']) . '<br />' . (!empty($_SESSION['ban']['expire_time']) ? sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)) : $txt['your_ban_expires_never']) . '<br />' . $txt['ban_continue_browse'], 'user');
 412	}
 413
 414	// Fix up the banning permissions.
 415	if (isset($user_info['permissions']))
 416		banPermissions();
 417}
 418
 419/**
 420 * Fix permissions according to ban status.
 421 * Applies any states of banning by removing permissions the user cannot have.
 422 */
 423function banPermissions()
 424{
 425	global $user_info, $modSettings, $context;
 426
 427	// Somehow they got here, at least take away all permissions...
 428	if (isset($_SESSION['ban']['cannot_access']))
 429		$user_info['permissions'] = array();
 430	// Okay, well, you can watch, but don't touch a thing.
 431	elseif (isset($_SESSION['ban']['cannot_post']) || (!empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $user_info['warning']))
 432	{
 433		$denied_permissions = array(
 434			'pm_send',
 435			'calendar_post', 'calendar_edit_own', 'calendar_edit_any',
 436			'poll_post',
 437			'poll_add_own', 'poll_add_any',
 438			'poll_edit_own', 'poll_edit_any',
 439			'poll_lock_own', 'poll_lock_any',
 440			'poll_remove_own', 'poll_remove_any',
 441			'manage_attachments', 'manage_smileys', 'manage_boards', 'admin_forum', 'manage_permissions',
 442			'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news',
 443			'profile_identity_any', 'profile_extra_any', 'profile_title_any',
 444			'post_new', 'post_reply_own', 'post_reply_any',
 445			'delete_own', 'delete_any', 'delete_replies',
 446			'make_sticky',
 447			'merge_any', 'split_any',
 448			'modify_own', 'modify_any', 'modify_replies',
 449			'move_any',
 450			'send_topic',
 451			'lock_own', 'lock_any',
 452			'remove_own', 'remove_any',
 453			'post_unapproved_topics', 'post_unapproved_replies_own', 'post_unapproved_replies_any',
 454		);
 455		call_integration_hook('integrate_post_ban_permissions', array($denied_permissions));
 456		$user_info['permissions'] = array_diff($user_info['permissions'], $denied_permissions);
 457	}
 458	// Are they absolutely under moderation?
 459	elseif (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $user_info['warning'])
 460	{
 461		// Work out what permissions should change...
 462		$permission_change = array(
 463			'post_new' => 'post_unapproved_topics',
 464			'post_reply_own' => 'post_unapproved_replies_own',
 465			'post_reply_any' => 'post_unapproved_replies_any',
 466			'post_attachment' => 'post_unapproved_attachments',
 467		);
 468		call_integration_hook('integrate_warn_permissions', array($permission_change));
 469		foreach ($permission_change as $old => $new)
 470		{
 471			if (!in_array($old, $user_info['permissions']))
 472				unset($permission_change[$old]);
 473			else
 474				$user_info['permissions'][] = $new;
 475		}
 476		$user_info['permissions'] = array_diff($user_info['permissions'], array_keys($permission_change));
 477	}
 478
 479	// @todo Find a better place to call this? Needs to be after permissions loaded!
 480	// Finally, some bits we cache in the session because it saves queries.
 481	if (isset($_SESSION['mc']) && $_SESSION['mc']['time'] > $modSettings['settings_updated'] && $_SESSION['mc']['id'] == $user_info['id'])
 482		$user_info['mod_cache'] = $_SESSION['mc'];
 483	else
 484	{
 485		require_once(SUBSDIR . '/Auth.subs.php');
 486		rebuildModCache();
 487	}
 488
 489	// Now that we have the mod cache taken care of lets setup a cache for the number of mod reports still open
 490	if (isset($_SESSION['rc']) && $_SESSION['rc']['time'] > $modSettings['last_mod_report_action'] && $_SESSION['rc']['id'] == $user_info['id'])
 491		$context['open_mod_reports'] = $_SESSION['rc']['reports'];
 492	elseif ($_SESSION['mc']['bq'] != '0=1')
 493	{
 494		require_once(SUBSDIR . '/Moderation.subs.php');
 495		recountOpenReports();
 496	}
 497	else
 498		$context['open_mod_reports'] = 0;
 499}
 500
 501/**
 502 * Log a ban in the database.
 503 * Log the current user in the ban logs.
 504 * Increment the hit counters for the specified ban ID's (if any.)
 505 *
 506 * @param array $ban_ids = array()
 507 * @param string $email = null
 508 */
 509function log_ban($ban_ids = array(), $email = null)
 510{
 511	global $user_info, $smcFunc;
 512
 513	// Don't log web accelerators, it's very confusing...
 514	if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch')
 515		return;
 516
 517	$smcFunc['db_insert']('',
 518		'{db_prefix}log_banned',
 519		array('id_member' => 'int', 'ip' => 'string-16', 'email' => 'string', 'log_time' => 'int'),
 520		array($user_info['id'], $user_info['ip'], ($email === null ? ($user_info['is_guest'] ? '' : $user_info['email']) : $email), time()),
 521		array('id_ban_log')
 522	);
 523
 524	// One extra point for these bans.
 525	if (!empty($ban_ids))
 526		$smcFunc['db_query']('', '
 527			UPDATE {db_prefix}ban_items
 528			SET hits = hits + 1
 529			WHERE id_ban IN ({array_int:ban_ids})',
 530			array(
 531				'ban_ids' => $ban_ids,
 532			)
 533		);
 534}
 535
 536/**
 537 * Checks if a given email address might be banned.
 538 * Check if a given email is banned.
 539 * Performs an immediate ban if the turns turns out positive.
 540 *
 541 * @param string $email
 542 * @param string $restriction
 543 * @param string $error
 544 */
 545function isBannedEmail($email, $restriction, $error)
 546{
 547	global $txt, $smcFunc;
 548
 549	// Can't ban an empty email
 550	if (empty($email) || trim($email) == '')
 551		return;
 552
 553	// Let's start with the bans based on your IP/hostname/memberID...
 554	$ban_ids = isset($_SESSION['ban'][$restriction]) ? $_SESSION['ban'][$restriction]['ids'] : array();
 555	$ban_reason = isset($_SESSION['ban'][$restriction]) ? $_SESSION['ban'][$restriction]['reason'] : '';
 556
 557	// ...and add to that the email address you're trying to register.
 558	$request = $smcFunc['db_query']('', '
 559		SELECT bi.id_ban, bg.' . $restriction . ', bg.cannot_access, bg.reason
 560		FROM {db_prefix}ban_items AS bi
 561			INNER JOIN {db_prefix}ban_groups AS bg ON (bg.id_ban_group = bi.id_ban_group)
 562		WHERE {string:email} LIKE bi.email_address
 563			AND (bg.' . $restriction . ' = {int:cannot_access} OR bg.cannot_access = {int:cannot_access})
 564			AND (bg.expire_time IS NULL OR bg.expire_time >= {int:now})',
 565		array(
 566			'email' => $email,
 567			'cannot_access' => 1,
 568			'now' => time(),
 569		)
 570	);
 571	while ($row = $smcFunc['db_fetch_assoc']($request))
 572	{
 573		if (!empty($row['cannot_access']))
 574		{
 575			$_SESSION['ban']['cannot_access']['ids'][] = $row['id_ban'];
 576			$_SESSION['ban']['cannot_access']['reason'] = $row['reason'];
 577		}
 578		if (!empty($row[$restriction]))
 579		{
 580			$ban_ids[] = $row['id_ban'];
 581			$ban_reason = $row['reason'];
 582		}
 583	}
 584	$smcFunc['db_free_result']($request);
 585
 586	// You're in biiig trouble.  Banned for the rest of this session!
 587	if (isset($_SESSION['ban']['cannot_access']))
 588	{
 589		log_ban($_SESSION['ban']['cannot_access']['ids']);
 590		$_SESSION['ban']['last_checked'] = time();
 591
 592		fatal_error(sprintf($txt['your_ban'], $txt['guest_title']) . $_SESSION['ban']['cannot_access']['reason'], false);
 593	}
 594
 595	if (!empty($ban_ids))
 596	{
 597		// Log this ban for future reference.
 598		log_ban($ban_ids, $email);
 599		fatal_error($error . $ban_reason, false);
 600	}
 601}
 602
 603/**
 604 * Make sure the user's correct session was passed, and they came from here.
 605 * Checks the current session, verifying that the person is who he or she should be.
 606 * Also checks the referrer to make sure they didn't get sent here.
 607 * Depends on the disableCheckUA setting, which is usually missing.
 608 * Will check GET, POST, or REQUEST depending on the passed type.
 609 * Also optionally checks the referring action if passed. (note that the referring action must be by GET.)
 610 *
 611 * @param string $type = 'post' (post, get, request)
 612 * @param string $from_action = ''
 613 * @param bool $is_fatal = true
 614 * @return string the error message if is_fatal is false.
 615 */
 616function checkSession($type = 'post', $from_action = '', $is_fatal = true)
 617{
 618	global $sc, $modSettings, $boardurl;
 619
 620	// Is it in as $_POST['sc']?
 621	if ($type == 'post')
 622	{
 623		$check = isset($_POST[$_SESSION['session_var']]) ? $_POST[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_POST['sc']) ? $_POST['sc'] : null);
 624		if ($check !== $sc)
 625			$error = 'session_timeout';
 626	}
 627
 628	// How about $_GET['sesc']?
 629	elseif ($type === 'get')
 630	{
 631		$check = isset($_GET[$_SESSION['session_var']]) ? $_GET[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_GET['sesc']) ? $_GET['sesc'] : null);
 632		if ($check !== $sc)
 633			$error = 'session_verify_fail';
 634	}
 635
 636	// Or can it be in either?
 637	elseif ($type == 'request')
 638	{
 639		$check = isset($_GET[$_SESSION['session_var']]) ? $_GET[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_GET['sesc']) ? $_GET['sesc'] : (isset($_POST[$_SESSION['session_var']]) ? $_POST[$_SESSION['session_var']] : (empty($modSettings['strictSessionCheck']) && isset($_POST['sc']) ? $_POST['sc'] : null)));
 640
 641		if ($check !== $sc)
 642			$error = 'session_verify_fail';
 643	}
 644
 645	// Verify that they aren't changing user agents on us - that could be bad.
 646	if ((!isset($_SESSION['USER_AGENT']) || $_SESSION['USER_AGENT'] != $_SERVER['HTTP_USER_AGENT']) && empty($modSettings['disableCheckUA']))
 647		$error = 'session_verify_fail';
 648
 649	// Make sure a page with session check requirement is not being prefetched.
 650	if (isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch')
 651	{
 652		ob_end_clean();
 653		header('HTTP/1.1 403 Forbidden');
 654		die;
 655	}
 656
 657	// Check the referring site - it should be the same server at least!
 658	if (isset($_SESSION['request_referer']))
 659		$referrer = $_SESSION['request_referer'];
 660	else
 661		$referrer = isset($_SERVER['HTTP_REFERER']) ? @parse_url($_SERVER['HTTP_REFERER']) : array();
 662
 663	if (!empty($referrer['host']))
 664	{
 665		if (strpos($_SERVER['HTTP_HOST'], ':') !== false)
 666			$real_host = substr($_SERVER['HTTP_HOST'], 0, strpos($_SERVER['HTTP_HOST'], ':'));
 667		else
 668			$real_host = $_SERVER['HTTP_HOST'];
 669
 670		$parsed_url = parse_url($boardurl);
 671
 672		// Are global cookies on?  If so, let's check them ;).
 673		if (!empty($modSettings['globalCookies']))
 674		{
 675			if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $parsed_url['host'], $parts) == 1)
 676				$parsed_url['host'] = $parts[1];
 677
 678			if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $referrer['host'], $parts) == 1)
 679				$referrer['host'] = $parts[1];
 680
 681			if (preg_match('~(?:[^\.]+\.)?([^\.]{3,}\..+)\z~i', $real_host, $parts) == 1)
 682				$real_host = $parts[1];
 683		}
 684
 685		// Okay: referrer must either match parsed_url or real_host.
 686		if (isset($parsed_url['host']) && strtolower($referrer['host']) != strtolower($parsed_url['host']) && strtolower($referrer['host']) != strtolower($real_host))
 687		{
 688			$error = 'verify_url_fail';
 689			$log_error = true;
 690		}
 691	}
 692
 693	// Well, first of all, if a from_action is specified you'd better have an old_url.
 694	if (!empty($from_action) && (!isset($_SESSION['old_url']) || preg_match('~[?;&]action=' . $from_action . '([;&]|$)~', $_SESSION['old_url']) == 0))
 695	{
 696		$error = 'verify_url_fail';
 697		$log_error = true;
 698	}
 699
 700	if (strtolower($_SERVER['HTTP_USER_AGENT']) == 'hacker')
 701		fatal_error('Sound the alarm!  It\'s a hacker!  Close the castle gates!!', false);
 702
 703	// Everything is ok, return an empty string.
 704	if (!isset($error))
 705		return '';
 706	// A session error occurred, show the error.
 707	elseif ($is_fatal)
 708	{
 709		if (isset($_GET['xml']))
 710		{
 711			ob_end_clean();
 712			header('HTTP/1.1 403 Forbidden - Session timeout');
 713			die;
 714		}
 715		else
 716			fatal_lang_error($error, isset($log_error) ? 'user' : false);
 717	}
 718	// A session error occurred, return the error to the calling function.
 719	else
 720		return $error;
 721
 722	// We really should never fall through here, for very important reasons.  Let's make sure.
 723	trigger_error('Hacking attempt...', E_USER_ERROR);
 724}
 725
 726/**
 727 * Check if a specific confirm parameter was given.
 728 *
 729 * @param string $action
 730 */
 731function checkConfirm($action)
 732{
 733	global $modSettings;
 734
 735	if (isset($_GET['confirm']) && isset($_SESSION['confirm_' . $action]) && md5($_GET['confirm'] . $_SERVER['HTTP_USER_AGENT']) === $_SESSION['confirm_' . $action])
 736		return true;
 737	else
 738	{
 739		$token = md5(mt_rand() . session_id() . (string) microtime() . $modSettings['rand_seed']);
 740		$_SESSION['confirm_' . $action] = md5($token . $_SERVER['HTTP_USER_AGENT']);
 741
 742		return $token;
 743	}
 744}
 745
 746/**
 747 * Lets give you a token of our appreciation.
 748 *
 749 * @param string $action
 750 * @param string $type = 'post'
 751 * @return array
 752 */
 753function createToken($action, $type = 'post')
 754{
 755	global $modSettings, $context;
 756
 757	$token = md5(mt_rand() . session_id() . (string) microtime() . $modSettings['rand_seed'] . $type);
 758	$token_var = substr(preg_replace('~^\d+~', '', md5(mt_rand() . (string) microtime() . mt_rand())), 0, rand(7, 12));
 759
 760	$_SESSION['token'][$type . '-' . $action] = array($token_var, md5($token . $_SERVER['HTTP_USER_AGENT']), time(), $token);
 761
 762	$context[$action . '_token'] = $token;
 763	$context[$action . '_token_var'] = $token_var;
 764
 765	return array($action . '_token_var' => $token_var, $action . '_token' => $token);
 766}
 767
 768/**
 769 * Only patrons with valid tokens can ride this ride.
 770 *
 771 * @param string $action
 772 * @param string $type = 'post' (get, request, or post)
 773 * @param bool $reset = true
 774 * @return boolean
 775 */
 776function validateToken($action, $type = 'post', $reset = true)
 777{
 778	global $modSettings;
 779
 780	$type = $type == 'get' || $type == 'request' ? $type : 'post';
 781
 782	// Logins are special: the token is used to has the password with javascript before POST it
 783	if ($action == 'login')
 784	{
 785		if (isset($_SESSION['token'][$type . '-' . $action]))
 786		{
 787			$return = $_SESSION['token'][$type . '-' . $action][3];
 788			unset($_SESSION['token'][$type . '-' . $action]);
 789			return $return;
 790		}
 791		else
 792			return '';
 793	}
 794
 795	// This nasty piece of code validates a token.
 796	/*
 797		1. The token exists in session.
 798		2. The {$type} variable should exist.
 799		3. We concat the variable we received with the user agent
 800		4. Match that result against what is in the session.
 801		5. If it matchs, success, otherwise we fallout.
 802	*/
 803	if (isset($_SESSION['token'][$type . '-' . $action], $GLOBALS['_' . strtoupper($type)][$_SESSION['token'][$type . '-' . $action][0]]) && md5($GLOBALS['_' . strtoupper($type)][$_SESSION['token'][$type . '-' . $action][0]] . $_SERVER['HTTP_USER_AGENT']) === $_SESSION['token'][$type . '-' . $action][1])
 804	{
 805		// Invalidate this token now.
 806		unset($_SESSION['token'][$type . '-' . $action]);
 807
 808		return true;
 809	}
 810
 811	// Patrons with invalid tokens get the boot.
 812	if ($reset)
 813	{
 814		// Might as well do some cleanup on this.
 815		cleanTokens();
 816
 817		// I'm back baby.
 818		createToken($action, $type);
 819
 820		fatal_lang_error('token_verify_fail', false);
 821	}
 822	// Remove this token as its useless
 823	else
 824		unset($_SESSION['token'][$type . '-' . $action]);
 825
 826	// Randomly check if we should remove some older tokens.
 827	if (mt_rand(0, 138) == 23)
 828		cleanTokens();
 829
 830	return false;
 831}
 832
 833/**
 834 * Removes old unused tokens from session
 835 * defaults to 3 hours before a token is considered expired
 836 * if $complete = true will remove all tokens
 837 *
 838 * @param bool $complete = false
 839 */
 840function cleanTokens($complete = false)
 841{
 842	// We appreciate cleaning up after yourselves.
 843	if (!isset($_SESSION['token']))
 844		return;
 845
 846	// Clean up tokens, trying to give enough time still.
 847	foreach ($_SESSION['token'] as $key => $data)
 848		if ($data[2] + 10800 < time() || $complete)
 849			unset($_SESSION['token'][$key]);
 850}
 851
 852/**
 853 * Check whether a form has been submitted twice.
 854 * Registers a sequence number for a form.
 855 * Checks whether a submitted sequence number is registered in the current session.
 856 * Depending on the value of is_fatal shows an error or returns true or false.
 857 * Frees a sequence number from the stack after it's been checked.
 858 * Frees a sequence number without checking if action == 'free'.
 859 *
 860 * @param string $action
 861 * @param bool $is_fatal = true
 862 * @return boolean
 863 */
 864function checkSubmitOnce($action, $is_fatal = true)
 865{
 866	global $context;
 867
 868	if (!isset($_SESSION['forms']))
 869		$_SESSION['forms'] = array();
 870
 871	// Register a form number and store it in the session stack. (use this on the page that has the form.)
 872	if ($action == 'register')
 873	{
 874		$context['form_sequence_number'] = 0;
 875		while (empty($context['form_sequence_number']) || in_array($context['form_sequence_number'], $_SESSION['forms']))
 876			$context['form_sequence_number'] = mt_rand(1, 16000000);
 877	}
 878	// Check whether the submitted number can be found in the session.
 879	elseif ($action == 'check')
 880	{
 881		if (!isset($_REQUEST['seqnum']))
 882			return true;
 883		elseif (!in_array($_REQUEST['seqnum'], $_SESSION['forms']))
 884		{
 885			$_SESSION['forms'][] = (int) $_REQUEST['seqnum'];
 886			return true;
 887		}
 888		elseif ($is_fatal)
 889			fatal_lang_error('error_form_already_submitted', false);
 890		else
 891			return false;
 892	}
 893	// Don't check, just free the stack number.
 894	elseif ($action == 'free' && isset($_REQUEST['seqnum']) && in_array($_REQUEST['seqnum'], $_SESSION['forms']))
 895		$_SESSION['forms'] = array_diff($_SESSION['forms'], array($_REQUEST['seqnum']));
 896	elseif ($action != 'free')
 897		trigger_error('checkSubmitOnce(): Invalid action \'' . $action . '\'', E_USER_WARNING);
 898}
 899
 900/**
 901 * Check the user's permissions.
 902 * checks whether the user is allowed to do permission. (ie. post_new.)
 903 * If boards is specified, checks those boards instead of the current one.
 904 * Always returns true if the user is an administrator.
 905 *
 906 * @param string $permission
 907 * @param array $boards = null
 908 * @return boolean if the user can do the permission
 909 */
 910function allowedTo($permission, $boards = null)
 911{
 912	global $user_info, $modSettings, $smcFunc;
 913
 914	// You're always allowed to do nothing. (unless you're a working man, MR. LAZY :P!)
 915	if (empty($permission))
 916		return true;
 917
 918	// You're never allowed to do something if your data hasn't been loaded yet!
 919	if (empty($user_info))
 920		return false;
 921
 922	// Administrators are supermen :P.
 923	if ($user_info['is_admin'])
 924		return true;
 925
 926	// Are we checking the _current_ board, or some other boards?
 927	if ($boards === null)
 928	{
 929		// Check if they can do it.
 930		if (!is_array($permission) && in_array($permission, $user_info['permissions']))
 931			return true;
 932		// Search for any of a list of permissions.
 933		elseif (is_array($permission) && count(array_intersect($permission, $user_info['permissions'])) != 0)
 934			return true;
 935		// You aren't allowed, by default.
 936		else
 937			return false;
 938	}
 939	elseif (!is_array($boards))
 940		$boards = array($boards);
 941
 942	$request = $smcFunc['db_query']('', '
 943		SELECT MIN(bp.add_deny) AS add_deny
 944		FROM {db_prefix}boards AS b
 945			INNER JOIN {db_prefix}board_permissions AS bp ON (bp.id_profile = b.id_profile)
 946			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
 947		WHERE b.id_board IN ({array_int:board_list})
 948			AND bp.id_group IN ({array_int:group_list}, {int:moderator_group})
 949			AND bp.permission {raw:permission_list}
 950			AND (mods.id_member IS NOT NULL OR bp.id_group != {int:moderator_group})
 951		GROUP BY b.id_board',
 952		array(
 953			'current_member' => $user_info['id'],
 954			'board_list' => $boards,
 955			'group_list' => $user_info['groups'],
 956			'moderator_group' => 3,
 957			'permission_list' => (is_array($permission) ? 'IN (\'' . implode('\', \'', $permission) . '\')' : ' = \'' . $permission . '\''),
 958		)
 959	);
 960
 961	// Make sure they can do it on all of the boards.
 962	if ($smcFunc['db_num_rows']($request) != count($boards))
 963		return false;
 964
 965	$result = true;
 966	while ($row = $smcFunc['db_fetch_assoc']($request))
 967		$result &= !empty($row['add_deny']);
 968	$smcFunc['db_free_result']($request);
 969
 970	// If the query returned 1, they can do it... otherwise, they can't.
 971	return $result;
 972}
 973
 974/**
 975 * Fatal error if they cannot.
 976 * Uses allowedTo() to check if the user is allowed to do permission.
 977 * Checks the passed boards or current board for the permission.
 978 * If they are not, it loads the Errors language file and shows an error using $txt['cannot_' . $permission].
 979 * If they are a guest and cannot do it, this calls is_not_guest().
 980 *
 981 * @param string $permission
 982 * @param array $boards = null
 983 */
 984function isAllowedTo($permission, $boards = null)
 985{
 986	global $user_info, $txt;
 987
 988	static $heavy_permissions = array(
 989		'admin_forum',
 990		'manage_attachments',
 991		'manage_smileys',
 992		'manage_boards',
 993		'edit_news',
 994		'moderate_forum',
 995		'manage_bans',
 996		'manage_membergroups',
 997		'manage_permissions',
 998	);
 999
1000	// Make it an array, even if a string was passed.
1001	$permission = is_array($permission) ? $permission : array($permission);
1002
1003	// Check the permission and return an error...
1004	if (!allowedTo($permission, $boards))
1005	{
1006		// Pick the last array entry as the permission shown as the error.
1007		$error_permission = array_shift($permission);
1008
1009		// If they are a guest, show a login. (because the error might be gone if they do!)
1010		if ($user_info['is_guest'])
1011		{
1012			loadLanguage('Errors');
1013			is_not_guest($txt['cannot_' . $error_permission]);
1014		}
1015
1016		// Clear the action because they aren't really doing that!
1017		$_GET['action'] = '';
1018		$_GET['board'] = '';
1019		$_GET['topic'] = '';
1020		writeLog(true);
1021
1022		fatal_lang_error('cannot_' . $error_permission, false);
1023
1024		// Getting this far is a really big problem, but let's try our best to prevent any cases...
1025		trigger_error('Hacking attempt...', E_USER_ERROR);
1026	}
1027
1028	// If you're doing something on behalf of some "heavy" permissions, validate your session.
1029	// (take out the heavy permissions, and if you can't do anything but those, you need a validated session.)
1030	if (!allowedTo(array_diff($permission, $heavy_permissions), $boards))
1031		validateSession();
1032}
1033
1034/**
1035 * Return the boards a user has a certain (board) permission on. (array(0) if all.)
1036 *  - returns a list of boards on which the user is allowed to do the specified permission.
1037 *  - returns an array with only a 0 in it if the user has permission to do this on every board.
1038 *  - returns an empty array if he or she cannot do this on any board.
1039 * If check_access is true will also make sure the group has proper access to that board.
1040 *
1041 * @param array $permissions
1042 * @param bool $check_access = true
1043 * @param bool $simple = true
1044 */
1045function boardsAllowedTo($permissions, $check_access = true, $simple = true)
1046{
1047	global $user_info, $modSettings, $smcFunc;
1048
1049	// Arrays are nice, most of the time.
1050	if (!is_array($permissions))
1051		$permissions = array($permissions);
1052
1053	/*
1054	 * Set $simple to true to use this function in compatability mode
1055	 * Otherwise, the resultant array becomes split into the multiple
1056	 * permissions that were passed. Other than that, it's just the normal
1057	 * state of play that you're used to.
1058	 */
1059
1060	// Administrators are all powerful, sorry.
1061	if ($user_info['is_admin'])
1062	{
1063		if ($simple)
1064			return array(0);
1065		else
1066		{
1067			$boards = array();
1068			foreach ($permissions as $permission)
1069				$boards[$permission] = array(0);
1070
1071			return $boards;
1072		}
1073	}
1074
1075	// All groups the user is in except 'moderator'.
1076	$groups = array_diff($user_info['groups'], array(3));
1077
1078	$request = $smcFunc['db_query']('', '
1079		SELECT b.id_board, bp.add_deny' . ($simple ? '' : ', bp.permission') . '
1080		FROM {db_prefix}board_permissions AS bp
1081			INNER JOIN {db_prefix}boards AS b ON (b.id_profile = bp.id_profile)
1082			LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board AND mods.id_member = {int:current_member})
1083		WHERE bp.id_group IN ({array_int:group_list}, {int:moderator_group})
1084			AND bp.permission IN ({array_string:permissions})
1085			AND (mods.id_member IS NOT NULL OR bp.id_group != {int:moderator_group})' .
1086			($check_access ? ' AND {query_see_board}' : ''),
1087		array(
1088			'current_member' => $user_info['id'],
1089			'group_list' => $groups,
1090			'moderator_group' => 3,
1091			'permissions' => $permissions,
1092		)
1093	);
1094	$boards = array();
1095	$deny_boards = array();
1096	while ($row = $smcFunc['db_fetch_assoc']($request))
1097	{
1098		if ($simple)
1099		{
1100			if (empty($row['add_deny']))
1101				$deny_boards[] = $row['id_board'];
1102			else
1103				$boards[] = $row['id_board'];
1104		}
1105		else
1106		{
1107			if (empty($row['add_deny']))
1108				$deny_boards[$row['permission']][] = $row['id_board'];
1109			else
1110				$boards[$row['permission']][] = $row['id_board'];
1111		}
1112	}
1113	$smcFunc['db_free_result']($request);
1114
1115	if ($simple)
1116		$boards = array_unique(array_values(array_diff($boards, $deny_boards)));
1117	else
1118	{
1119		foreach ($permissions as $permission)
1120		{
1121			// never had it to start with
1122			if (empty($boards[$permission]))
1123				$boards[$permission] = array();
1124			else
1125			{
1126				// Or it may have been removed
1127				$deny_boards[$permission] = isset($deny_boards[$permission]) ? $deny_boards[$permission] : array();
1128				$boards[$permission] = array_unique(array_values(array_diff($boards[$permission], $deny_boards[$permission])));
1129			}
1130		}
1131	}
1132
1133	return $boards;
1134}
1135
1136/**
1137 * Returns whether an email address should be shown and how.
1138 * Possible outcomes are
1139 *  'yes': show the full email address
1140 *  'yes_permission_override': show the full email address, either you
1141 *   are a moderator or it's your own email address.
1142 *  'no_through_forum': don't show the email address, but do allow
1143 *    things to be mailed using the built-in forum mailer.
1144 *  'no': keep the email address hidden.
1145 *
1146 * @param bool $userProfile_hideEmail
1147 * @param int $userProfile_id
1148 * @return string (yes, yes_permission_override, no_through_forum, no)
1149 */
1150function showEmailAddress($userProfile_hideEmail, $userProfile_id)
1151{
1152	global $modSettings, $user_info;
1153
1154	// Should this user's email address be shown?
1155	// If you're guest and the forum is set to hide email for guests: no.
1156	// If the user is post-banned: no.
1157	// If it's your own profile and you've set your address hidden: yes_permission_override.
1158	// If you're a moderator with sufficient permissions: yes_permission_override.
1159	// If the user has set their email address to be hidden: no.
1160	// If the forum is set to show full email addresses: yes.
1161	// Otherwise: no_through_forum.
1162
1163	if ((!empty($modSettings['guest_hideContacts']) && $user_info['is_guest']) || isset($_SESSION['ban']['cannot_post']))
1164		return 'no';
1165	elseif ((!$user_info['is_guest'] && $user_info['id'] == $userProfile_id && !$userProfile_hideEmail) || allowedTo('moderate_forum'))
1166		return 'yes_permission_override';
1167	elseif ($userProfile_hideEmail)
1168		return 'no';
1169	elseif (!empty($modSettings['make_email_viewable']) )
1170		return 'yes';
1171	else
1172		return 'no_through_forum';
1173}
1174
1175/**
1176 * This function attempts to protect from spammed messages and the like.
1177 * The time taken depends on error_type - generally uses the modSetting.
1178 *
1179 * @param string $error_type used also as a $txt index. (not an actual string.)
1180 * @return boolean
1181 */
1182function spamProtection($error_type)
1183{
1184	global $modSettings, $txt, $user_info, $smcFunc;
1185
1186	// Certain types take less/more time.
1187	$timeOverrides = array(
1188		'login' => 2,
1189		'register' => 2,
1190		'remind' => 30,
1191		'sendtopic' => $modSettings['spamWaitTime'] * 4,
1192		'sendmail' => $modSettings['spamWaitTime'] * 5,
1193		'reporttm' => $modSettings['spamWaitTime'] * 4,
1194		'search' => !empty($modSettings['search_floodcontrol_time']) ? $modSettings['search_floodcontrol_time'] : 1,
1195	);
1196	call_integration_hook('integrate_spam_protection', array($timeOverrides));
1197
1198	// Moderators are free...
1199	if (!allowedTo('moderate_board'))
1200		$timeLimit = isset($timeOverrides[$error_type]) ? $timeOverrides[$error_type] : $modSettings['spamWaitTime'];
1201	else
1202		$timeLimit = 2;
1203
1204	// Delete old entries...
1205	$smcFunc['db_query']('', '
1206		DELETE FROM {db_prefix}log_floodcontrol
1207		WHERE log_time < {int:log_time}
1208			AND log_type = {string:log_type}',
1209		array(
1210			'log_time' => time() - $timeLimit,
1211			'log_type' => $error_type,
1212		)
1213	);
1214
1215	// Add a new entry, deleting the old if necessary.
1216	$smcFunc['db_insert']('replace',
1217		'{db_prefix}log_floodcontrol',
1218		array('ip' => 'string-16', 'log_time' => 'int', 'log_type' => 'string'),
1219		array($user_info['ip'], time(), $error_type),
1220		array('ip', 'log_type')
1221	);
1222
1223	// If affected is 0 or 2, it was there already.
1224	if ($smcFunc['db_affected_rows']() != 1)
1225	{
1226		// Spammer!  You only have to wait a *few* seconds!
1227		fatal_lang_error($error_type . '_WaitTime_broken', false, array($timeLimit));
1228		return true;
1229	}
1230
1231	// They haven't posted within the limit.
1232	return false;
1233}
1234
1235/**
1236 * A generic function to create a pair of index.php and .htaccess files in a directory
1237 *
1238 * @param string $path the (absolute) directory path
1239 * @param boolean $attachments if the directory is an attachments directory or not
1240 * @return true on success error string if anything fails
1241 */
1242function secureDirectory($path, $attachments = false)
1243{
1244	if (empty($path))
1245		return 'empty_path';
1246
1247	if (!is_writable($path))
1248		return 'path_not_writable';
1249
1250	$directoryname = basename($path);
1251
1252	$errors = array();
1253	$close = empty($attachments) ? '
1254</Files>' : '
1255	Allow from localhost
1256</Files>
1257
1258RemoveHandler .php .php3 .phtml .cgi .fcgi .pl .fpl .shtml';
1259
1260	if (file_exists($path . '/.htaccess'))
1261		$errors[] = 'htaccess_exists';
1262	else
1263	{
1264		$fh = @fopen($path . '/.htaccess', 'w');
1265		if ($fh) {
1266			fwrite($fh, '<Files *>
1267	Order Deny,Allow
1268	Deny from all' . $close);
1269			fclose($fh);
1270		}
1271		$errors[] = 'htaccess_cannot_create_file';
1272	}
1273
1274	if (file_exists($path . '/index.php'))
1275		$errors[] = 'index-php_exists';
1276	else
1277	{
1278		$fh = @fopen($path . '/index.php', 'w');
1279		if ($fh) {
1280			fwrite($fh, '<?php
1281
1282/**
1283 * This file is here solely to protect your ' . $directoryname . ' directory.
1284 */
1285
1286// Look for Settings.php....
1287if (file_exists(dirname(dirname(__FILE__)) . \'/Settings.php\'))
1288{
1289	// Found it!
1290	require(dirname(dirname(__FILE__)) . \'/Settings.php\');
1291	header(\'Location: \' . $boardurl);
1292}
1293// Can\'t find it... just forget it.
1294else
1295	exit;
1296');
1297			fclose($fh);
1298		}
1299		$errors[] = 'index-php_cannot_create_file';
1300	}
1301
1302	if (!empty($errors))
1303		return $errors;
1304	else
1305		return true;
1306}
1307
1308/**
1309 * Helper function that puts together a ban query for a given ip
1310 * builds the query for ipv6, ipv4 or 255.255.255.255 depending on whats supplied
1311 *
1312 * @param string $fullip An IP address either IPv6 or not
1313 * @return string A SQL condition
1314 */
1315function constructBanQueryIP($fullip)
1316{
1317	// First attempt a IPv6 address.
1318	if (isValidIPv6($fullip))
1319	{
1320		$ip_parts = convertIPv6toInts($fullip);
1321
1322		$ban_query = '((' . $ip_parts[0] . ' BETWEEN bi.ip_low1 AND bi.ip_high1)
1323			AND (' . $ip_parts[1] . ' BETWEEN bi.ip_low2 AND bi.ip_high2)
1324			AND (' . $ip_parts[2] . ' BETWEEN bi.ip_low3 AND bi.ip_high3)
1325			AND (' . $ip_parts[3] . ' BETWEEN bi.ip_low4 AND bi.ip_high4)
1326			AND (' . $ip_parts[4] . ' BETWEEN bi.ip_low5 AND bi.ip_high5)
1327			AND (' . $ip_parts[5] . ' BETWEEN bi.ip_low6 AND bi.ip_high6)
1328			AND (' . $ip_parts[6] . ' BETWEEN bi.ip_low7 AND bi.ip_high7)
1329			AND (' . $ip_parts[7] . ' BETWEEN bi.ip_low8 AND bi.ip_high8))';
1330	}
1331	// Check if we have a valid IPv4 address.
1332	elseif (preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $fullip, $ip_parts) == 1)
1333		$ban_query = '((' . $ip_parts[1] . ' BETWEEN bi.ip_low1 AND bi.ip_high1)
1334			AND (' . $ip_parts[2] . ' BETWEEN bi.ip_low2 AND bi.ip_high2)
1335			AND (' . $ip_parts[3] . ' BETWEEN bi.ip_low3 AND bi.ip_high3)
1336			AND (' . $ip_parts[4] . ' BETWEEN bi.ip_low4 AND bi.ip_high4))';
1337	// We use '255.255.255.255' for 'unknown' since it's not valid anyway.
1338	else
1339		$ban_query = '(bi.ip_low1 = 255 AND bi.ip_high1 = 255
1340			AND bi.ip_low2 = 255 AND bi.ip_high2 = 255
1341			AND bi.ip_low3 = 255 AND bi.ip_high3 = 255
1342			AND bi.ip_low4 = 255 AND bi.ip_high4 = 255)';
1343
1344	return $ban_query;
1345}
1346
1347/**
1348 * Decide if we are going to enable bad behavior scanning for this user
1349 * - Admins and Moderators get a free pass
1350 * - Optionally existing users with post counts over a limit are bypassed
1351 * - Others get a humane frisking
1352 */
1353function loadBadBehavior()
1354{
1355	global $modSettings, $user_info, $context, $bb2_results;
1356
1357	$bb_run = false;
1358
1359	// Bad Behavior Enabled?
1360	if (!empty($modSettings['badbehavior_enabled']))
1361	{
1362		require_once(EXTDIR . '/bad-behavior/badbehavior-plugin.php');
1363		$bb_run = true;
1364
1365		// We may want to give some folks a hallway pass
1366		if (!$user_info['is_guest'])
1367		{
1368			if (!empty($user_info['is_mod']) || !empty($user_info['is_admin']))
1369				$bb_run = false;
1370			elseif (!empty($modSettings['badbehavior_postcount_wl']) && $modSettings['badbehavior_postcount_wl'] < 0)
1371				$bb_run = false;
1372			elseif (!empty($modSettings['badbehavior_postcount_wl']) && $modSettings['badbehavior_postcount_wl'] > 0 && ($user_info['posts'] > $modSettings['badbehavior_postcount_wl']))
1373				$bb_run = false;
1374		}
1375
1376		// Put on the sanitary gloves, its time for a patdown !
1377		if ($bb_run === true)
1378		{
1379			$bb2_results = bb2_start(bb2_read_settings());
1380			addInlineJavascript(bb2_insert_head());
1381		}
1382	}
1383}
1384
1385/**
1386 * This protects against brute force attacks on a member's password.
1387 * Importantly, even if the password was right we DON'T TELL THEM!
1388 *
1389 * @param $id_member
1390 * @param $password_flood_value = false
1391 * @param $was_correct = false
1392 */
1393function validatePasswordFlood($id_member, $password_flood_value = false, $was_correct = false)
1394{
1395	global $smcFunc, $cookiename;
1396
1397	// As this is only brute protection, we allow 5 attempts every 10 seconds.
1398
1399	// Destroy any session or cookie data about this member, as they validated wrong.
1400	require_once(SUBSDIR . '/Auth.subs.php');
1401	setLoginCookie(-3600, 0);
1402
1403	if (isset($_SESSION['login_' . $cookiename]))
1404		unset($_SESSION['login_' . $cookiename]);
1405
1406	// We need a member!
1407	if (!$id_member)
1408	{
1409		// Redirect back!
1410		redirectexit();
1411
1412		// Probably not needed, but still make sure...
1413		fatal_lang_error('no_access', false);
1414	}
1415
1416	// Right, have we got a flood value?
1417	if ($password_flood_value !== false)
1418		@list ($time_stamp, $number_tries) = explode('|', $password_flood_value);
1419
1420	// Timestamp invalid or non-existent?
1421	if (empty($number_tries) || $time_stamp < (time() - 10))
1422	{
1423		// If it wasn't *that* long ago, don't give them another five goes.
1424		$number_tries = !empty($number_tries) && $time_stamp < (time() - 20) ? 2 : 0;
1425		$time_stamp = time();
1426	}
1427
1428	$number_tries++;
1429
1430	// Broken the law?
1431	if ($number_tries > 5)
1432		fatal_lang_error('login_threshold_brute_fail', 'critical');
1433
1434	// Otherwise set the members data. If they correct on their first attempt then we actually clear it, otherwise we set it!
1435	updateMemberData($id_member, array('passwd_flood' => $was_correct && $number_tries == 1 ? '' : $time_stamp . '|' . $number_tries));
1436
1437}