Please make sure IN_MYBB is defined.");
}
$plugins->add_hook("datahandler_login_validate_start", "loginconvert_convert", 1);
global $valid_login_types, $utf8_recheck;
// Array of supported login types
$valid_login_types = array(
	"vb3"		=> "vb",		// Module isn't supported anymore, but old merges may require it
	"vb4"		=> "vb",
	"vb5"		=> "vb5",
	"ipb2"		=> "ipb",		// Module isn't supported anymore, but old merges may require it
	"ipb3"		=> "ipb",
	"ipb4"		=> "ipb4",
	"smf"		=> "smf",		// Isn't supported anymore, but the function is still required by smf 1.1 and 2 and there may be "old" users
	"smf11"		=> "smf11",
	"smf2"		=> "smf2",
	"punbb"		=> "punbb",
	"phpbb3"	=> "phpbb3",
	"bbpress"	=> "bbpress",
	"xf"		=> "xf11",		// XenForo can have two different authentications
	"xf12"		=> "xf12",		// 1.2+ use PHP's crypt function by default
	"wbb3"		=> "wcf1",		// WBB 3 and Lite 2 use WoltLab Community Framework 1.x with some special parameters
	"wbb4"		=> "wcf2",		// WBB 4 uses WoltLab Community Framework 2.x
	"vanilla"	=> "vanilla",
	"fluxbb"	=> "punbb",		// FluxBB is a fork of PunBB and they didn't change the hashing part
);
// Array of login types for which we need to handle utf8 issues
$utf8_recheck = array(
	'smf',
	'smf11',
	'smf2',
	'vb',
);
function loginconvert_info()
{
	global $db;
	$info = array(
		"name"				=> "Login Password Conversion",
		"description"		=> "Converts passwords to the correct type when logging in. To be used in conjunction with the MyBB Merge System.",
		"website"			=> "http://www.mybb.com",
		"author"			=> "MyBB Group",
		"authorsite"		=> "http://www.mybb.com",
		"version"			=> "1.4.2",
		"compatibility"		=> "18*",
		"codename"			=> "loginconvert",
	);
	if($db->field_exists("passwordconvert", "users"))
	{
		// Checks whether the plugin is really needed
		$query = $db->simple_select("users", "uid", "passwordconvert IS NOT NULL AND passwordconvert!=''", array("limit" => 1));
		if($db->num_rows($query) > 0)
		{
			$info['description'] .= "
This plugin should be activated as there are users with unconverted password.";
		}
		else
		{
			$info['description'] .= "
This plugin can be deactivated and deleted as all passwords are converted.";
		}
	}
	else
	{
		$info['description'] .= "
Please delete the file \"inc/plugins/loginconvert.php\"";
	}
	return $info;
}
function loginconvert_activate()
{
	global $db;
	// Don't activate the plugin if it isn't needed
	if(!$db->field_exists("passwordconvert", "users"))
	{
		flash_message("There's no need to activate this plugin as there aren't any passwords which need to be converted", "error");
		admin_redirect("index.php?module=config-plugins");
	}
}
function loginconvert_deactivate()
{
	global $db;
	// Remove the columns if all passwords have been converted
	$query = $db->simple_select("users", "uid", "passwordconvert IS NOT NULL AND passwordconvert!=''", array("limit" => 1));
	if($db->num_rows($query) == 0)
	{
		$db->drop_column("users", "passwordconvert");
		$db->drop_column("users", "passwordconverttype");
		$db->drop_column("users", "passwordconvertsalt");
	}
}
/**
 * @param LoginDataHandler $login
 */
function loginconvert_convert(&$login)
{
	global $mybb, $valid_login_types, $utf8_recheck, $db, $settings;
	$options = array(
		"fields" => array('username', "password", "salt", 'loginkey', 'coppauser', 'usergroup', "passwordconvert", "passwordconverttype", "passwordconvertsalt"),
		"username_method" => (int)$settings['username_method']
	);
	if($login->username_method !== null)
	{
		$options['username_method'] = (int)$login->username_method;
	}
	$user = get_user_by_username($login->data['username'], $options);
	// There's nothing to check for, let MyBB do everything
	// This fails also when no user was found above, so no need for an extra check
	if(!isset($user['passwordconvert']) || $user['passwordconvert'] == '')
	{
		return;
	}
	// This user has already a mybb generated hash, delete the merge system data
	// Happens eg after resetting password or getting a new one via the acp
	if(!empty($user['password']))
	{
		$update = array(
			"passwordconvert" => "",
			"passwordconverttype" => "",
			"passwordconvertsalt" => ""
		);
		$db->update_query("users", $update, "uid={$user['uid']}");
		return;
	}
	if(!array_key_exists($user['passwordconverttype'], $valid_login_types))
	{
		// TODO: Is there an easy way to make the error translatable without adding a new language file?
		redirect($mybb->settings['bburl']."/member.php?action=lostpw", "We're sorry but we couldn't convert your old password. Please select a new one", "", true);
	}
	else
	{
		$login_type = $valid_login_types[$user['passwordconverttype']];
		$function = "check_{$login_type}";
		$check = $function($login->data['password'], $user);
		// If the password was wrong, an utf8 password and we want to check utf8 passwords we call the function again
		if(!$check && in_array($login_type, $utf8_recheck) && utf8_decode($login->data['password']) != $login->data['password'])
		{
			$check = $function(utf8_decode($login->data['password']), $user);
		}
		if(!$check)
		{
			// Make sure the password isn't tested again
			unset($login->data['password']);
			// Yeah, that function is called later too, but we need to know whether the captcha is right
			// If we wouldn't call that function the error would always be shown
			$login->verify_attempts($mybb->settings['captchaimage']);
			$login->invalid_combination(true);
		}
		else
		{
			// The password was correct, so use MyBB's method the next time (even if the captcha was wrong we can update the password)
			$salt = generate_salt();
			$update = array(
				"salt"					=> $salt,
				"password"				=> salt_password(md5($login->data['password']), $salt),
				"loginkey"				=> generate_loginkey(),
				"passwordconverttype"	=> "",
				"passwordconvert"		=> "",
				"passwordconvertsalt"	=> "",
			);
			$db->update_query("users", $update, "uid='{$user['uid']}'");
			// Make sure the password isn't tested again
			unset($login->data['password']);
			// Also make sure all data is available when creating the session (otherwise SQL errors -.-)
			$login->login_data = array_merge($user, $update);
		}
	}
}
// Password functions
function check_vb($password, $user)
{
	if(md5(md5($password).$user['passwordconvertsalt']) == $user['passwordconvert'] || md5($password.$user['passwordconvertsalt']) == $user['passwordconvert'])
	{
		return true;
	}
	return false;
}
function check_vb5($password, $user)
{
	if ($user['passwordconvert'] == crypt($password, $user['passwordconvert']))
	{
		return true;
	}
	return false;
}
function check_ipb($password, $user)
{
	// The salt was saved in the "salt" column on IPB 2 but we changed it in IPB 3 to use the correct "passwordconvertsalt" column
	if(!empty($user['passwordconvertsalt']))
	{
		$salt = $user['passwordconvertsalt'];
	}
	else if(!empty($user['salt']))
	{
		$salt = $user['salt'];
	}
	else
	{
		return false;
	}
	if($user['passwordconvert'] == md5(md5($salt).md5($password)))
	{
		return true;
	}
	return false;
}
function check_ipb4($password, $user)
{
	if($user['passwordconvert'] == crypt($password, '$2a$13$'.$user['passwordconvertsalt']))
	{
		return true;
	}
	return false;
}
function check_smf($password, $user)
{
	if(crypt($password, substr($password, 0, 2)) == $user['passwordconvert'])
	{
		return true;
	}
	else if(my_strlen($user['passwordconvert']) == 32 && md5_hmac(preg_replace("#\_smf1\.1\_import(\d+)$#i", '', $user['username']), $password) == $user['passwordconvert'])
	{
		return true;
	}
	else if(my_strlen($user['passwordconvert']) == 32 && md5($password) == $user['passwordconvert'])
	{
		return true;
	}
	return false;
}
function check_smf11($password, $user)
{
	if(my_strlen($user['passwordconvert']) == 40)
	{
		$is_sha1 = true;
	}
	else
	{
		$is_sha1 = false;
	}
	if($is_sha1 && sha1(strtolower(preg_replace("#\_smf1\.1\_import(\d+)$#i", '', $user['username'])).$password) == $user['passwordconvert'])
	{
		return true;
	}
	else
	{
		return check_smf($password, $user);
	}
}
function check_smf2($password, $user)
{
	if(my_strlen($user['passwordconvert']) == 40)
	{
		$is_sha1 = true;
	}
	else
	{
		$is_sha1 = false;
	}
	if($is_sha1 && sha1(strtolower(preg_replace("#\_smf2\.0\_import(\d+)$#i", '', $user['username'])).$password) == $user['passwordconvert'])
	{
		return true;
	}
	else
	{
		return check_smf($password, $user);
	}
}
function check_punbb($password, $user)
{
	if(my_strlen($user['passwordconvert']) == 40)
	{
		$is_sha1 = true;
	}
	else
	{
		$is_sha1 = false;
	}
	if(function_exists('sha1') && $is_sha1 && (sha1($password) == $user['passwordconvert'] || sha1($user['passwordconvertsalt'].sha1($password)) == $user['passwordconvert']))
	{
		return true;
	}
	elseif(function_exists('mhash') && $is_sha1 && (bin2hex(mhash(MHASH_SHA1, $password)) == $user['passwordconvert'] || bin2hex(mhash(MHASH_SHA1, $user['passwordconvertsalt'].bin2hex(mhash(MHASH_SHA1, $password)))) == $user['passwordconvert']))
	{
		return true;
	}
	else if(md5($password) == $user['passwordconvert'])
	{
		return true;
	}
	return false;
}
function check_phpbb3($password, $user)
{
	// The bcrypt hash is at least 60 chars and is used in phpBB 3.1
	if (my_strlen($user['passwordconvert']) >= 60 && $user['passwordconvert'] == crypt($password, $user['passwordconvert']))
	{
		return true;
	}
	// The rest here is phpBB 3.0
	else if (my_strlen($user['passwordconvert']) == 34)
	{
		if(phpbb3_crypt_private($password, $user['passwordconvert']) === $user['passwordconvert'])
		{
			return true;
		}
		return false;
	}
    if(md5($password) === $user['passwordconvert'])
	{
		return true;
	}
	return false;
}
function check_bbpress($password, $user)
{
	// WordPress (and so bbPress) used simple md5 hashing some time ago
	if ( strlen($user['passwordconvert']) <= 32 )
	{
		return ($user['passwordconvert'] == md5($password));
	}
	else
	{
		$hash = bbpress_crypt_private($password, $user['passwordconvert']);
		if ($hash[0] == '*')
		{
			$hash = crypt($password, $user['passwordconvert']);
		}
		return $hash === $user['passwordconvert'];
	}
}
function check_xf11($password, $user)
{
	$hash = xf_hash(xf_hash($password));
	return ($hash === $user['passwordconvert']);
}
function check_xf12($password, $user)
{
	if ($user['passwordconvert'] == crypt($password, $user['passwordconvert']))
	{
		return true;
	}
	return false;
}
function check_wcf1($password, $user)
{
	// WCF 1 has some special parameters, which are saved in the passwordconvert field
	$settings = my_unserialize($user['passwordconvert']);
	$user['passwordconvert'] = $settings['password'];
	if(wcf1_encrypt($user['passwordconvertsalt'].wcf1_hash($password, $user['passwordconvertsalt'], $settings), $settings['encryption_method']) == $user['passwordconvert'])
	{
		return true;
	}
	return false;
}
function check_wcf2($password, $user)
{
	// WCF 2 doesn't save the salt in a seperate column and it's easier to fetch it when it's needed than doing it while merging
	$salt = mb_substr($user['passwordconvert'], 0, 29);
	return (crypt(crypt($password, $salt), $salt) == $user['passwordconvert']);
}
function check_vanilla($password, $user)
{
	if($user['passwordconvert'][0] === '_' || $user['passwordconvert'][0] === '$')
	{
		$hash = vanilla_crypt_private($password, $user['passwordconvert']);
		if ($hash[0] == '*')
		{
			$hash = crypt($password, $user['passwordconvert']);
		}
		return $hash == $user['passwordconvert'];
	}
	else if($password && $user['passwordconvert'] !== '*' && ($password === $user['passwordconvert'] || md5($password) === $user['passwordconvert']))
	{
		return true;
	}
	return false;
}
/************************************
 * Helpers used by different boards *
 ************************************/
// Used by WCF1
function wcf1_encrypt($value, $method) {
	switch ($method) {
		case 'sha1': return sha1($value);
		case 'md5': return md5($value);
		case 'crc32': return crc32($value);
		case 'crypt': return crypt($value);
		default: return $value;
	}
}
function wcf1_hash($value, $salt, $settings) {
	if ($settings['encryption_enable_salting']) {
		$hash = '';
		// salt
		if ($settings['encryption_salt_position'] == 'before') {
			$hash .= $salt;
		}
		// value
		if ($settings['encryption_encrypt_before_salting']) {
			$hash .= wcf1_encrypt($value, $settings['encryption_method']);
		}
		else {
			$hash .= $value;
		}
		// salt
		if ($settings['encryption_salt_position'] == 'after') {
			$hash .= $salt;
		}
		return wcf1_encrypt($hash, $settings['encryption_method']);
	}
	else {
		return wcf1_encrypt($value, $settings['encryption_method']);
	}
}
// Used by XenForo 1.0 and 1.1
function xf_hash($data)
{
	if (extension_loaded('hash'))
	{
		return hash('sha256', $data);
	}
	else
	{
		return sha1($data);
	}
}
// Used by SMF 1.0
function md5_hmac($username, $password)
{
	if(my_strlen($username) > 64)
	{
		$username = pack('H*', md5($username));
	}
	$username = str_pad($username, 64, chr(0x00));
	$k_ipad = $username ^ str_repeat(chr(0x36), 64);
	$k_opad = $username ^ str_repeat(chr(0x5c), 64);
	return md5($k_opad.pack('H*', md5($k_ipad.$password)));
}
/********************************************************
 * phpass functions - first the crypt_private functions *
 * then the encoding function used by all boards        *
 ********************************************************/
// Used by bbPress
function bbpress_crypt_private($password, $setting)
{
	$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
	$output = '*0';
	if (substr($setting, 0, 2) == $output)
		$output = '*1';
	if (substr($setting, 0, 3) != '$P$')
		return $output;
	$count_log2 = strpos($itoa64, $setting[3]);
	if ($count_log2 < 7 || $count_log2 > 30)
		return $output;
	$count = 1 << $count_log2;
	$salt = substr($setting, 4, 8);
	if (strlen($salt) != 8)
		return $output;
	# We're kind of forced to use MD5 here since it's the only
	# cryptographic primitive available in all versions of PHP
	# currently in use.  To implement our own low-level crypto
	# in PHP would result in much worse performance and
	# consequently in lower iteration counts and hashes that are
	# quicker to crack (by non-PHP code).
	if (PHP_VERSION >= '5') {
		$hash = md5($salt . $password, TRUE);
		do {
			$hash = md5($hash . $password, TRUE);
		} while (--$count);
	} else {
		$hash = pack('H*', md5($salt . $password));
		do {
			$hash = pack('H*', md5($hash . $password));
		} while (--$count);
	}
	$output = substr($setting, 0, 12);
	$output .= _hash_encode64($hash, 16, $itoa64);
	return $output;
}
// Used by phpBB 3
function phpbb3_crypt_private($password, $setting)
{
	$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
	$output = '*';
	// Check for correct hash
	if (substr($setting, 0, 3) != '$H$')
	{
		return $output;
	}
	$count_log2 = strpos($itoa64, $setting[3]);
	if ($count_log2 < 7 || $count_log2 > 30)
	{
		return $output;
	}
	$count = 1 << $count_log2;
	$salt = substr($setting, 4, 8);
	if (my_strlen($salt) != 8)
	{
		return $output;
	}
	/**
	* We're kind of forced to use MD5 here since it's the only
	* cryptographic primitive available in all versions of PHP
	* currently in use.  To implement our own low-level crypto
	* in PHP would result in much worse performance and
	* consequently in lower iteration counts and hashes that are
	* quicker to crack (by non-PHP code).
	*/
	if (PHP_VERSION >= 5)
	{
		$hash = md5($salt . $password, true);
		do
		{
			$hash = md5($hash . $password, true);
		}
		while (--$count);
	}
	else
	{
		$hash = pack('H*', md5($salt . $password));
		do
		{
			$hash = pack('H*', md5($hash . $password));
		}
		while (--$count);
	}
	$output = substr($setting, 0, 12);
	$output .= _hash_encode64($hash, 16, $itoa64);
	return $output;
}
// Used by Vanilla
function vanilla_crypt_private($password, $setting)
{
	$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
	$output = '*0';
	if (substr($setting, 0, 2) == $output)
		$output = '*1';
	if (substr($setting, 0, 3) != '$P$')
		return $output;
	$count_log2 = strpos($itoa64, $setting[3]);
	if ($count_log2 < 7 || $count_log2 > 30)
		return $output;
	$count = 1 << $count_log2;
	$salt = substr($setting, 4, 8);
	if (strlen($salt) != 8)
		return $output;
	# We're kind of forced to use MD5 here since it's the only
	# cryptographic primitive available in all versions of PHP
	# currently in use.  To implement our own low-level crypto
	# in PHP would result in much worse performance and
	# consequently in lower iteration counts and hashes that are
	# quicker to crack (by non-PHP code).
	if (PHP_VERSION >= '5') {
		$hash = md5($salt . $password, TRUE);
		do {
			$hash = md5($hash . $password, TRUE);
		} while (--$count);
	} else {
		$hash = pack('H*', md5($salt . $password));
		do {
			$hash = pack('H*', md5($hash . $password));
		} while (--$count);
	}
	$output = substr($setting, 0, 12);
	$output .= _hash_encode64($hash, 16, $itoa64);
	return $output;
}
/**
* The BELOW code falls under public domain, allowing its use in MyBB for this script
* and can be redistributed under the GNU General Public License.
*/
/**
*
* @version Version 0.1
*
* Portable PHP password hashing framework.
*
* Written by Solar Designer  in 2004-2006 and placed in
* the public domain.
*
* There's absolutely no warranty.
*
* The homepage URL for this framework is:
*
*	http://www.openwall.com/phpass/
*
* Please be sure to update the Version line if you edit this file in any way.
* It is suggested that you leave the main version number intact, but indicate
* your project name (after the slash) and add your own revision information.
*
* Please do not change the "private" password hashing method implemented in
* here, thereby making your hashes incompatible.  However, if you must, please
* change the hash type identifier (the "$P$") to something different.
*
* Obviously, since this code is in the public domain, the above are not
* requirements (there can be none), but merely suggestions.
*
*/
/**
* Encode hash
*/
function _hash_encode64($input, $count, &$itoa64)
{
	$output = '';
	$i = 0;
	do
	{
		$value = ord($input[$i++]);
		$output .= $itoa64[$value & 0x3f];
		if ($i < $count)
		{
			$value |= ord($input[$i]) << 8;
		}
		$output .= $itoa64[($value >> 6) & 0x3f];
		if ($i++ >= $count)
		{
			break;
		}
		if ($i < $count)
		{
			$value |= ord($input[$i]) << 16;
		}
		$output .= $itoa64[($value >> 12) & 0x3f];
		if ($i++ >= $count)
		{
			break;
		}
		$output .= $itoa64[($value >> 18) & 0x3f];
	}
	while ($i < $count);
	return $output;
}