123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639 |
- <?php
- /**
- * Handle all of the OpenID interfacing and communications.
- * Simple Machines Forum (SMF)
- *
- * @package SMF
- * @author Simple Machines http://www.simplemachines.org
- * @copyright 2012 Simple Machines Forum contributors
- * @license http://www.simplemachines.org/about/smf/license.php BSD
- *
- * @version 2.1 Alpha 1
- */
- if (!defined('SMF'))
- die('Hacking attempt...');
- /**
- * Openid_uri is the URI given by the user
- * Validates the URI and changes it to a fully canonicalize URL
- * Determines the IDP server and delegation
- * Optional array of fields to restore when validation complete.
- * Redirects the user to the IDP for validation
- * Enter description here ...
- * @param string $openid_uri
- * @param bool $return = false
- * @param array $save_fields = array()
- * @param string $return_action = null
- * @return string
- */
- function smf_openID_validate($openid_uri, $return = false, $save_fields = array(), $return_action = null)
- {
- global $sourcedir, $scripturl, $boardurl, $modSettings;
- $openid_url = smf_openID_canonize($openid_uri);
- $response_data = smf_openID_getServerInfo($openid_url);
- if ($response_data === false)
- return 'no_data';
- if (($assoc = smf_openID_getAssociation($response_data['server'])) == null)
- $assoc = smf_openID_makeAssociation($response_data['server']);
- // Before we go wherever it is we are going, store the GET and POST data, because it might be useful when we get back.
- $request_time = time();
- // Just in case they are doing something else at this time.
- while (isset($_SESSION['openid']['saved_data'][$request_time]))
- $request_time = md5($request_time);
- $_SESSION['openid']['saved_data'][$request_time] = array(
- 'get' => $_GET,
- 'post' => $_POST,
- 'openid_uri' => $openid_url,
- 'cookieTime' => $modSettings['cookieTime'],
- );
- $parameters = array(
- 'openid.mode=checkid_setup',
- 'openid.trust_root=' . urlencode($scripturl),
- 'openid.identity=' . urlencode(empty($response_data['delegate']) ? $openid_url : $response_data['delegate']),
- 'openid.assoc_handle=' . urlencode($assoc['handle']),
- 'openid.return_to=' . urlencode($scripturl . '?action=openidreturn&sa=' . (!empty($return_action) ? $return_action : $_REQUEST['action']) . '&t=' . $request_time . (!empty($save_fields) ? '&sf=' . base64_encode(serialize($save_fields)) : '')),
- );
- // If they are logging in but don't yet have an account or they are registering, let's request some additional information
- if (($_REQUEST['action'] == 'login2' && !smf_openid_member_exists($openid_url)) || ($_REQUEST['action'] == 'register' || $_REQUEST['action'] == 'register2'))
- {
- // Email is required.
- $parameters[] = 'openid.sreg.required=email';
- // The rest is just optional.
- $parameters[] = 'openid.sreg.optional=nickname,dob,gender';
- }
- $redir_url = $response_data['server'] . '?' . implode('&', $parameters);
- if ($return)
- return $redir_url;
- else
- redirectexit($redir_url);
- }
- /**
- * Revalidate a user using OpenID. Note that this function will not return when authentication is required.
- * @return bool
- */
- function smf_openID_revalidate()
- {
- global $user_settings;
- if (isset($_SESSION['openid_revalidate_time']) && $_SESSION['openid_revalidate_time'] > time() - 60)
- {
- unset($_SESSION['openid_revalidate_time']);
- return true;
- }
- else
- smf_openID_validate($user_settings['openid_uri'], false, null, 'revalidate');
- // We shouldn't get here.
- trigger_error('Hacking attempt...', E_USER_ERROR);
- }
- /**
- * @todo Enter description here ...
- * @param string $server
- * @param string $handle = null
- * @param bool $no_delete = false
- * @return array
- */
- function smf_openID_getAssociation($server, $handle = null, $no_delete = false)
- {
- global $smcFunc;
- if (!$no_delete)
- {
- // Delete the already expired associations.
- $smcFunc['db_query']('openid_delete_assoc_old', '
- DELETE FROM {db_prefix}openid_assoc
- WHERE expires <= {int:current_time}',
- array(
- 'current_time' => time(),
- )
- );
- }
- // Get the association that has the longest lifetime from now.
- $request = $smcFunc['db_query']('openid_select_assoc', '
- SELECT server_url, handle, secret, issued, expires, assoc_type
- FROM {db_prefix}openid_assoc
- WHERE server_url = {string:server_url}' . ($handle === null ? '' : '
- AND handle = {string:handle}') . '
- ORDER BY expires DESC',
- array(
- 'server_url' => $server,
- 'handle' => $handle,
- )
- );
- if ($smcFunc['db_num_rows']($request) == 0)
- return null;
- $return = $smcFunc['db_fetch_assoc']($request);
- $smcFunc['db_free_result']($request);
- return $return;
- }
- /**
- * @todo Enter description here ...
- * @param string $server
- * @return array
- */
- function smf_openID_makeAssociation($server)
- {
- global $smcFunc, $modSettings, $p;
- $parameters = array(
- 'openid.mode=associate',
- );
- // We'll need to get our keys for the Diffie-Hellman key exchange.
- $dh_keys = smf_openID_setup_DH();
- // If we don't support DH we'll have to see if the provider will accept no encryption.
- if ($dh_keys === false)
- $parameters[] = 'openid.session_type=';
- else
- {
- $parameters[] = 'openid.session_type=DH-SHA1';
- $parameters[] = 'openid.dh_consumer_public=' . urlencode(base64_encode(long_to_binary($dh_keys['public'])));
- $parameters[] = 'openid.assoc_type=HMAC-SHA1';
- }
- // The data to post to the server.
- $post_data = implode('&', $parameters);
- $data = fetch_web_data($server, $post_data);
- // Parse the data given.
- preg_match_all('~^([^:]+):(.+)$~m', $data, $matches);
- $assoc_data = array();
- foreach ($matches[1] as $key => $match)
- $assoc_data[$match] = $matches[2][$key];
- if (!isset($assoc_data['assoc_type']) || (empty($assoc_data['mac_key']) && empty($assoc_data['enc_mac_key'])))
- fatal_lang_error('openid_server_bad_response');
- // Clean things up a bit.
- $handle = isset($assoc_data['assoc_handle']) ? $assoc_data['assoc_handle'] : '';
- $issued = time();
- $expires = $issued + min((int)$assoc_data['expires_in'], 60);
- $assoc_type = isset($assoc_data['assoc_type']) ? $assoc_data['assoc_type'] : '';
- // @todo Is this really needed?
- foreach (array('dh_server_public', 'enc_mac_key') as $key)
- if (isset($assoc_data[$key]))
- $assoc_data[$key] = str_replace(' ', '+', $assoc_data[$key]);
- // Figure out the Diffie-Hellman secret.
- if (!empty($assoc_data['enc_mac_key']))
- {
- $dh_secret = bcpowmod(binary_to_long(base64_decode($assoc_data['dh_server_public'])), $dh_keys['private'], $p);
- $secret = base64_encode(binary_xor(sha1(long_to_binary($dh_secret), true), base64_decode($assoc_data['enc_mac_key'])));
- }
- else
- $secret = $assoc_data['mac_key'];
- // Store the data
- $smcFunc['db_insert']('replace',
- '{db_prefix}openid_assoc',
- array('server_url' => 'string', 'handle' => 'string', 'secret' => 'string', 'issued' => 'int', 'expires' => 'int', 'assoc_type' => 'string'),
- array($server, $handle, $secret, $issued, $expires, $assoc_type),
- array('server_url', 'handle')
- );
- return array(
- 'server' => $server,
- 'handle' => $assoc_data['assoc_handle'],
- 'secret' => $secret,
- 'issued' => $issued,
- 'expires' => $expires,
- 'assoc_type' => $assoc_data['assoc_type'],
- );
- }
- function smf_openID_removeAssociation($handle)
- {
- global $smcFunc;
- $smcFunc['db_query']('openid_remove_association', '
- DELETE FROM {db_prefix}openid_assoc
- WHERE handle = {string:handle}',
- array(
- 'handle' => $handle,
- )
- );
- }
- function smf_openID_return()
- {
- global $smcFunc, $user_info, $user_profile, $sourcedir, $modSettings, $context, $sc, $user_settings;
- // Is OpenID even enabled?
- if (empty($modSettings['enableOpenID']))
- fatal_lang_error('no_access', false);
- if (!isset($_GET['openid_mode']))
- fatal_lang_error('openid_return_no_mode', false);
- // @todo Check for error status!
- if ($_GET['openid_mode'] != 'id_res')
- fatal_lang_error('openid_not_resolved');
- // SMF has this annoying habit of removing the + from the base64 encoding. So lets put them back.
- foreach (array('openid_assoc_handle', 'openid_invalidate_handle', 'openid_sig', 'sf') as $key)
- if (isset($_GET[$key]))
- $_GET[$key] = str_replace(' ', '+', $_GET[$key]);
- // Did they tell us to remove any associations?
- if (!empty($_GET['openid_invalidate_handle']))
- smf_openid_removeAssociation($_GET['openid_invalidate_handle']);
- $server_info = smf_openid_getServerInfo($_GET['openid_identity']);
- // Get the association data.
- $assoc = smf_openID_getAssociation($server_info['server'], $_GET['openid_assoc_handle'], true);
- if ($assoc === null)
- fatal_lang_error('openid_no_assoc');
- $secret = base64_decode($assoc['secret']);
- $signed = explode(',', $_GET['openid_signed']);
- $verify_str = '';
- foreach ($signed as $sign)
- {
- $verify_str .= $sign . ':' . strtr($_GET['openid_' . str_replace('.', '_', $sign)], array('&' => '&')) . "\n";
- }
- $verify_str = base64_encode(sha1_hmac($verify_str, $secret));
- if ($verify_str != $_GET['openid_sig'])
- {
- fatal_lang_error('openid_sig_invalid', 'critical');
- }
- if (!isset($_SESSION['openid']['saved_data'][$_GET['t']]))
- fatal_lang_error('openid_load_data');
- $openid_uri = $_SESSION['openid']['saved_data'][$_GET['t']]['openid_uri'];
- $modSettings['cookieTime'] = $_SESSION['openid']['saved_data'][$_GET['t']]['cookieTime'];
- if (empty($openid_uri))
- fatal_lang_error('openid_load_data');
- // Any save fields to restore?
- $context['openid_save_fields'] = isset($_GET['sf']) ? unserialize(base64_decode($_GET['sf'])) : array();
- // Is there a user with this OpenID_uri?
- $result = $smcFunc['db_query']('', '
- SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt,
- openid_uri
- FROM {db_prefix}members
- WHERE openid_uri = {string:openid_uri}',
- array(
- 'openid_uri' => $openid_uri,
- )
- );
- $member_found = $smcFunc['db_num_rows']($result);
- if (!$member_found && isset($_GET['sa']) && $_GET['sa'] == 'change_uri' && !empty($_SESSION['new_openid_uri']) && $_SESSION['new_openid_uri'] == $openid_uri)
- {
- // Update the member.
- updateMemberData($user_settings['id_member'], array('openid_uri' => $openid_uri));
- unset($_SESSION['new_openid_uri']);
- $_SESSION['openid'] = array(
- 'verified' => true,
- 'openid_uri' => $openid_uri,
- );
- // Send them back to profile.
- redirectexit('action=profile;area=authentication;updated');
- }
- elseif (!$member_found)
- {
- // Store the received openid info for the user when returned to the registration page.
- $_SESSION['openid'] = array(
- 'verified' => true,
- 'openid_uri' => $openid_uri,
- );
- if (isset($_GET['openid_sreg_nickname']))
- $_SESSION['openid']['nickname'] = $_GET['openid_sreg_nickname'];
- if (isset($_GET['openid_sreg_email']))
- $_SESSION['openid']['email'] = $_GET['openid_sreg_email'];
- if (isset($_GET['openid_sreg_dob']))
- $_SESSION['openid']['dob'] = $_GET['openid_sreg_dob'];
- if (isset($_GET['openid_sreg_gender']))
- $_SESSION['openid']['gender'] = $_GET['openid_sreg_gender'];
- // Were we just verifying the registration state?
- if (isset($_GET['sa']) && $_GET['sa'] == 'register2')
- {
- require_once($sourcedir . '/Register.php');
- return Register2(true);
- }
- else
- redirectexit('action=register');
- }
- elseif (isset($_GET['sa']) && $_GET['sa'] == 'revalidate' && $user_settings['openid_uri'] == $openid_uri)
- {
- $_SESSION['openid_revalidate_time'] = time();
- // Restore the get data.
- require_once($sourcedir . '/Subs-Auth.php');
- $_SESSION['openid']['saved_data'][$_GET['t']]['get']['openid_restore_post'] = $_GET['t'];
- $query_string = construct_query_string($_SESSION['openid']['saved_data'][$_GET['t']]['get']);
- redirectexit($query_string);
- }
- else
- {
- $user_settings = $smcFunc['db_fetch_assoc']($result);
- $smcFunc['db_free_result']($result);
- $user_settings['passwd'] = sha1(strtolower($user_settings['member_name']) . $secret);
- $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4);
- updateMemberData($user_settings['id_member'], array('passwd' => $user_settings['passwd'], 'password_salt' => $user_settings['password_salt']));
- // Cleanup on Aisle 5.
- $_SESSION['openid'] = array(
- 'verified' => true,
- 'openid_uri' => $openid_uri,
- );
- require_once($sourcedir . '/LogInOut.php');
- if (!checkActivation())
- return;
- DoLogin();
- }
- }
- /**
- * @todo Enter description here ...
- * @param string $uri
- */
- function smf_openID_canonize($uri)
- {
- // @todo Add in discovery.
- if (strpos($uri, 'http://') !== 0 && strpos($uri, 'https://') !== 0)
- $uri = 'http://' . $uri;
- if (strpos(substr($uri, strpos($uri, '://') + 3), '/') === false)
- $uri .= '/';
- return $uri;
- }
- /**
- * @todo Enter description here ...
- * @param string $uri
- * @return array
- */
- function smf_openid_member_exists($url)
- {
- global $smcFunc;
- $request = $smcFunc['db_query']('openid_member_exists', '
- SELECT mem.id_member, mem.member_name
- FROM {db_prefix}members AS mem
- WHERE mem.openid_uri = {string:openid_uri}',
- array(
- 'openid_uri' => $url,
- )
- );
- $member = $smcFunc['db_fetch_assoc']($request);
- $smcFunc['db_free_result']($request);
- return $member;
- }
- /**
- * Prepare for a Diffie-Hellman key exchange.
- * @param bool $regenerate = false
- * @return array|false return false on failure or an array() on success
- */
- function smf_openID_setup_DH($regenerate = false)
- {
- global $p, $g;
- // First off, do we have BC Math available?
- if (!function_exists('bcpow'))
- return false;
- // Defined in OpenID spec.
- $p = '155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443';
- $g = '2';
- // Make sure the scale is set.
- bcscale(0);
- return smf_openID_get_keys($regenerate);
- }
- /**
- * @todo Enter description here ...
- * @param bool $regenerate
- */
- function smf_openID_get_keys($regenerate)
- {
- global $modSettings, $p, $g;
- // Ok lets take the easy way out, are their any keys already defined for us? They are changed in the daily maintenance scheduled task.
- if (!empty($modSettings['dh_keys']) && !$regenerate)
- {
- // Sweeeet!
- list ($public, $private) = explode("\n", $modSettings['dh_keys']);
- return array(
- 'public' => base64_decode($public),
- 'private' => base64_decode($private),
- );
- }
- // Dang it, now I have to do math. And it's not just ordinary math, its the evil big interger math. This will take a few seconds.
- $private = smf_openid_generate_private_key();
- $public = bcpowmod($g, $private, $p);
- // Now that we did all that work, lets save it so we don't have to keep doing it.
- $keys = array('dh_keys' => base64_encode($public) . "\n" . base64_encode($private));
- updateSettings($keys);
- return array(
- 'public' => $public,
- 'private' => $private,
- );
- }
- /**
- * @todo Enter description here ...
- * @return float
- */
- function smf_openid_generate_private_key()
- {
- global $p;
- static $cache = array();
- $byte_string = long_to_binary($p);
- if (isset($cache[$byte_string]))
- list ($dup, $num_bytes) = $cache[$byte_string];
- else
- {
- $num_bytes = strlen($byte_string) - ($byte_string[0] == "\x00" ? 1 : 0);
- $max_rand = bcpow(256, $num_bytes);
- $dup = bcmod($max_rand, $num_bytes);
- $cache[$byte_string] = array($dup, $num_bytes);
- }
- do
- {
- $str = '';
- for ($i = 0; $i < $num_bytes; $i += 4)
- $str .= pack('L', mt_rand());
- $bytes = "\x00" . $str;
- $num = binary_to_long($bytes);
- } while (bccomp($num, $dup) < 0);
- return bcadd(bcmod($num, $p), 1);
- }
- /**
- *
- * Enter description here ...
- * @param string $openid_url
- * @return bool|array
- */
- function smf_openID_getServerInfo($openid_url)
- {
- global $sourcedir;
- require_once($sourcedir . '/Subs-Package.php');
- // Get the html and parse it for the openid variable which will tell us where to go.
- $webdata = fetch_web_data($openid_url);
- if (empty($webdata))
- return false;
- $response_data = array();
- // Some OpenID servers have strange but still valid HTML which makes our job hard.
- if (preg_match_all('~<link([\s\S]*?)/?>~i', $webdata, $link_matches) == 0)
- fatal_lang_error('openid_server_bad_response');
- foreach ($link_matches[1] as $link_match)
- {
- if (preg_match('~rel="([\s\S]*?)"~i', $link_match, $rel_match) == 0 || preg_match('~href="([\s\S]*?)"~i', $link_match, $href_match) == 0)
- continue;
- $rels = preg_split('~\s+~', $rel_match[1]);
- foreach ($rels as $rel)
- if (preg_match('~openid2?\.(server|delegate|provider)~i', $rel, $match) != 0)
- $response_data[$match[1]] = $href_match[1];
- }
- if (empty($response_data['server']))
- if (empty($response_data['provider']))
- fatal_lang_error('openid_server_bad_response');
- else
- $response_data['server'] = $response_data['provider'];
- return $response_data;
- }
- /**
- * @param string $data
- * @param string $key
- * @return string
- */
- function sha1_hmac($data, $key)
- {
- if (strlen($key) > 64)
- $key = sha1($key, true);
- // Pad the key if need be.
- $key = str_pad($key, 64, chr(0x00));
- $ipad = str_repeat(chr(0x36), 64);
- $opad = str_repeat(chr(0x5c), 64);
- $hash1 = sha1(($key ^ $ipad) . $data, true);
- $hmac = sha1(($key ^ $opad) . $hash1, true);
- return $hmac;
- }
- function binary_to_long($str)
- {
- $bytes = array_merge(unpack('C*', $str));
- $n = 0;
- foreach ($bytes as $byte)
- {
- $n = bcmul($n, 256);
- $n = bcadd($n, $byte);
- }
- return $n;
- }
- function long_to_binary($value)
- {
- $cmp = bccomp($value, 0);
- if ($cmp < 0)
- fatal_error('Only non-negative integers allowed.');
- if ($cmp == 0)
- return "\x00";
- $bytes = array();
- while (bccomp($value, 0) > 0)
- {
- array_unshift($bytes, bcmod($value, 256));
- $value = bcdiv($value, 256);
- }
- if ($bytes && ($bytes[0] > 127))
- array_unshift($bytes, 0);
- $return = '';
- foreach ($bytes as $byte)
- $return .= pack('C', $byte);
- return $return;
- }
- /**
- * @param int $num1
- * @param int $num2
- */
- function binary_xor($num1, $num2)
- {
- $return = '';
- for ($i = 0; $i < strlen($num2); $i++)
- $return .= $num1[$i] ^ $num2[$i];
- return $return;
- }
- ?>
|