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;
}