PageRenderTime 70ms CodeModel.GetById 22ms app.highlight 38ms RepoModel.GetById 1ms app.codeStats 0ms

/Sources/Security.php

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