Chris@76
|
1 <?php
|
Chris@76
|
2
|
Chris@76
|
3 /**
|
Chris@76
|
4 * Simple Machines Forum (SMF)
|
Chris@76
|
5 *
|
Chris@76
|
6 * @package SMF
|
Chris@76
|
7 * @author Simple Machines http://www.simplemachines.org
|
Chris@76
|
8 * @copyright 2011 Simple Machines
|
Chris@76
|
9 * @license http://www.simplemachines.org/about/smf/license.php BSD
|
Chris@76
|
10 *
|
Chris@76
|
11 * @version 2.0
|
Chris@76
|
12 */
|
Chris@76
|
13
|
Chris@76
|
14 if (!defined('SMF'))
|
Chris@76
|
15 die('Hacking attempt...');
|
Chris@76
|
16
|
Chris@76
|
17 /* This file handles all of the OpenID interfacing and communications.
|
Chris@76
|
18 void smf_openID_validate(string openid_url, bool allow_immediate_validation = true)
|
Chris@76
|
19 - openid_uri is the URI given by the user
|
Chris@76
|
20 - Validates the URI and changes it to a fully canonicalize URL
|
Chris@76
|
21 - Determines the IDP server and delegation
|
Chris@76
|
22 - optional array of fields to restore when validation complete.
|
Chris@76
|
23 - Redirects the user to the IDP for validation
|
Chris@76
|
24 */
|
Chris@76
|
25
|
Chris@76
|
26 function smf_openID_validate($openid_uri, $return = false, $save_fields = array(), $return_action = null)
|
Chris@76
|
27 {
|
Chris@76
|
28 global $sourcedir, $scripturl, $boardurl, $modSettings;
|
Chris@76
|
29
|
Chris@76
|
30 $openid_url = smf_openID_canonize($openid_uri);
|
Chris@76
|
31
|
Chris@76
|
32 $response_data = smf_openID_getServerInfo($openid_url);
|
Chris@76
|
33 if ($response_data === false)
|
Chris@76
|
34 return 'no_data';
|
Chris@76
|
35
|
Chris@76
|
36 if (($assoc = smf_openID_getAssociation($response_data['server'])) == null)
|
Chris@76
|
37 $assoc = smf_openID_makeAssociation($response_data['server']);
|
Chris@76
|
38
|
Chris@76
|
39 // Before we go wherever it is we are going, store the GET and POST data, because it might be useful when we get back.
|
Chris@76
|
40 $request_time = time();
|
Chris@76
|
41 // Just in case they are doing something else at this time.
|
Chris@76
|
42 while (isset($_SESSION['openid']['saved_data'][$request_time]))
|
Chris@76
|
43 $request_time = md5($request_time);
|
Chris@76
|
44
|
Chris@76
|
45 $_SESSION['openid']['saved_data'][$request_time] = array(
|
Chris@76
|
46 'get' => $_GET,
|
Chris@76
|
47 'post' => $_POST,
|
Chris@76
|
48 'openid_uri' => $openid_url,
|
Chris@76
|
49 'cookieTime' => $modSettings['cookieTime'],
|
Chris@76
|
50 );
|
Chris@76
|
51
|
Chris@76
|
52 $parameters = array(
|
Chris@76
|
53 'openid.mode=checkid_setup',
|
Chris@76
|
54 'openid.trust_root=' . urlencode($scripturl),
|
Chris@76
|
55 'openid.identity=' . urlencode(empty($response_data['delegate']) ? $openid_url : $response_data['delegate']),
|
Chris@76
|
56 'openid.assoc_handle=' . urlencode($assoc['handle']),
|
Chris@76
|
57 '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)) : '')),
|
Chris@76
|
58 );
|
Chris@76
|
59
|
Chris@76
|
60 // If they are logging in but don't yet have an account or they are registering, let's request some additional information
|
Chris@76
|
61 if (($_REQUEST['action'] == 'login2' && !smf_openid_member_exists($openid_url)) || ($_REQUEST['action'] == 'register' || $_REQUEST['action'] == 'register2'))
|
Chris@76
|
62 {
|
Chris@76
|
63 // Email is required.
|
Chris@76
|
64 $parameters[] = 'openid.sreg.required=email';
|
Chris@76
|
65 // The rest is just optional.
|
Chris@76
|
66 $parameters[] = 'openid.sreg.optional=nickname,dob,gender';
|
Chris@76
|
67 }
|
Chris@76
|
68
|
Chris@76
|
69 $redir_url = $response_data['server'] . '?' . implode('&', $parameters);
|
Chris@76
|
70
|
Chris@76
|
71 if ($return)
|
Chris@76
|
72 return $redir_url;
|
Chris@76
|
73 else
|
Chris@76
|
74 redirectexit($redir_url);
|
Chris@76
|
75 }
|
Chris@76
|
76
|
Chris@76
|
77 // Revalidate a user using OpenID. Note that this function will not return when authentication is required.
|
Chris@76
|
78 function smf_openID_revalidate()
|
Chris@76
|
79 {
|
Chris@76
|
80 global $user_settings;
|
Chris@76
|
81
|
Chris@76
|
82 if (isset($_SESSION['openid_revalidate_time']) && $_SESSION['openid_revalidate_time'] > time() - 60)
|
Chris@76
|
83 {
|
Chris@76
|
84 unset($_SESSION['openid_revalidate_time']);
|
Chris@76
|
85 return true;
|
Chris@76
|
86 }
|
Chris@76
|
87 else
|
Chris@76
|
88 smf_openID_validate($user_settings['openid_uri'], false, null, 'revalidate');
|
Chris@76
|
89
|
Chris@76
|
90 // We shouldn't get here.
|
Chris@76
|
91 trigger_error('Hacking attempt...', E_USER_ERROR);
|
Chris@76
|
92 }
|
Chris@76
|
93
|
Chris@76
|
94 function smf_openID_getAssociation($server, $handle = null, $no_delete = false)
|
Chris@76
|
95 {
|
Chris@76
|
96 global $smcFunc;
|
Chris@76
|
97
|
Chris@76
|
98 if (!$no_delete)
|
Chris@76
|
99 {
|
Chris@76
|
100 // Delete the already expired associations.
|
Chris@76
|
101 $smcFunc['db_query']('openid_delete_assoc_old', '
|
Chris@76
|
102 DELETE FROM {db_prefix}openid_assoc
|
Chris@76
|
103 WHERE expires <= {int:current_time}',
|
Chris@76
|
104 array(
|
Chris@76
|
105 'current_time' => time(),
|
Chris@76
|
106 )
|
Chris@76
|
107 );
|
Chris@76
|
108 }
|
Chris@76
|
109
|
Chris@76
|
110 // Get the association that has the longest lifetime from now.
|
Chris@76
|
111 $request = $smcFunc['db_query']('openid_select_assoc', '
|
Chris@76
|
112 SELECT server_url, handle, secret, issued, expires, assoc_type
|
Chris@76
|
113 FROM {db_prefix}openid_assoc
|
Chris@76
|
114 WHERE server_url = {string:server_url}' . ($handle === null ? '' : '
|
Chris@76
|
115 AND handle = {string:handle}') . '
|
Chris@76
|
116 ORDER BY expires DESC',
|
Chris@76
|
117 array(
|
Chris@76
|
118 'server_url' => $server,
|
Chris@76
|
119 'handle' => $handle,
|
Chris@76
|
120 )
|
Chris@76
|
121 );
|
Chris@76
|
122
|
Chris@76
|
123 if ($smcFunc['db_num_rows']($request) == 0)
|
Chris@76
|
124 return null;
|
Chris@76
|
125
|
Chris@76
|
126 $return = $smcFunc['db_fetch_assoc']($request);
|
Chris@76
|
127 $smcFunc['db_free_result']($request);
|
Chris@76
|
128
|
Chris@76
|
129 return $return;
|
Chris@76
|
130 }
|
Chris@76
|
131
|
Chris@76
|
132 function smf_openID_makeAssociation($server)
|
Chris@76
|
133 {
|
Chris@76
|
134 global $smcFunc, $modSettings, $p;
|
Chris@76
|
135
|
Chris@76
|
136 $parameters = array(
|
Chris@76
|
137 'openid.mode=associate',
|
Chris@76
|
138 );
|
Chris@76
|
139
|
Chris@76
|
140 // We'll need to get our keys for the Diffie-Hellman key exchange.
|
Chris@76
|
141 $dh_keys = smf_openID_setup_DH();
|
Chris@76
|
142
|
Chris@76
|
143 // If we don't support DH we'll have to see if the provider will accept no encryption.
|
Chris@76
|
144 if ($dh_keys === false)
|
Chris@76
|
145 $parameters[] = 'openid.session_type=';
|
Chris@76
|
146 else
|
Chris@76
|
147 {
|
Chris@76
|
148 $parameters[] = 'openid.session_type=DH-SHA1';
|
Chris@76
|
149 $parameters[] = 'openid.dh_consumer_public=' . urlencode(base64_encode(long_to_binary($dh_keys['public'])));
|
Chris@76
|
150 $parameters[] = 'openid.assoc_type=HMAC-SHA1';
|
Chris@76
|
151 }
|
Chris@76
|
152
|
Chris@76
|
153 // The data to post to the server.
|
Chris@76
|
154 $post_data = implode('&', $parameters);
|
Chris@76
|
155 $data = fetch_web_data($server, $post_data);
|
Chris@76
|
156
|
Chris@76
|
157 // Parse the data given.
|
Chris@76
|
158 preg_match_all('~^([^:]+):(.+)$~m', $data, $matches);
|
Chris@76
|
159 $assoc_data = array();
|
Chris@76
|
160
|
Chris@76
|
161 foreach ($matches[1] as $key => $match)
|
Chris@76
|
162 $assoc_data[$match] = $matches[2][$key];
|
Chris@76
|
163
|
Chris@76
|
164 if (!isset($assoc_data['assoc_type']) || (empty($assoc_data['mac_key']) && empty($assoc_data['enc_mac_key'])))
|
Chris@76
|
165 fatal_lang_error('openid_server_bad_response');
|
Chris@76
|
166
|
Chris@76
|
167 // Clean things up a bit.
|
Chris@76
|
168 $handle = isset($assoc_data['assoc_handle']) ? $assoc_data['assoc_handle'] : '';
|
Chris@76
|
169 $issued = time();
|
Chris@76
|
170 $expires = $issued + min((int)$assoc_data['expires_in'], 60);
|
Chris@76
|
171 $assoc_type = isset($assoc_data['assoc_type']) ? $assoc_data['assoc_type'] : '';
|
Chris@76
|
172
|
Chris@76
|
173 // !!! Is this really needed?
|
Chris@76
|
174 foreach (array('dh_server_public', 'enc_mac_key') as $key)
|
Chris@76
|
175 if (isset($assoc_data[$key]))
|
Chris@76
|
176 $assoc_data[$key] = str_replace(' ', '+', $assoc_data[$key]);
|
Chris@76
|
177
|
Chris@76
|
178 // Figure out the Diffie-Hellman secret.
|
Chris@76
|
179 if (!empty($assoc_data['enc_mac_key']))
|
Chris@76
|
180 {
|
Chris@76
|
181 $dh_secret = bcpowmod(binary_to_long(base64_decode($assoc_data['dh_server_public'])), $dh_keys['private'], $p);
|
Chris@76
|
182 $secret = base64_encode(binary_xor(sha1_raw(long_to_binary($dh_secret)), base64_decode($assoc_data['enc_mac_key'])));
|
Chris@76
|
183 }
|
Chris@76
|
184 else
|
Chris@76
|
185 $secret = $assoc_data['mac_key'];
|
Chris@76
|
186
|
Chris@76
|
187 // Store the data
|
Chris@76
|
188 $smcFunc['db_insert']('replace',
|
Chris@76
|
189 '{db_prefix}openid_assoc',
|
Chris@76
|
190 array('server_url' => 'string', 'handle' => 'string', 'secret' => 'string', 'issued' => 'int', 'expires' => 'int', 'assoc_type' => 'string'),
|
Chris@76
|
191 array($server, $handle, $secret, $issued, $expires, $assoc_type),
|
Chris@76
|
192 array('server_url', 'handle')
|
Chris@76
|
193 );
|
Chris@76
|
194
|
Chris@76
|
195 return array(
|
Chris@76
|
196 'server' => $server,
|
Chris@76
|
197 'handle' => $assoc_data['assoc_handle'],
|
Chris@76
|
198 'secret' => $secret,
|
Chris@76
|
199 'issued' => $issued,
|
Chris@76
|
200 'expires' => $expires,
|
Chris@76
|
201 'assoc_type' => $assoc_data['assoc_type'],
|
Chris@76
|
202 );
|
Chris@76
|
203 }
|
Chris@76
|
204
|
Chris@76
|
205 function smf_openID_removeAssociation($handle)
|
Chris@76
|
206 {
|
Chris@76
|
207 global $smcFunc;
|
Chris@76
|
208
|
Chris@76
|
209 $smcFunc['db_query']('openid_remove_association', '
|
Chris@76
|
210 DELETE FROM {db_prefix}openid_assoc
|
Chris@76
|
211 WHERE handle = {string:handle}',
|
Chris@76
|
212 array(
|
Chris@76
|
213 'handle' => $handle,
|
Chris@76
|
214 )
|
Chris@76
|
215 );
|
Chris@76
|
216 }
|
Chris@76
|
217
|
Chris@76
|
218 function smf_openID_return()
|
Chris@76
|
219 {
|
Chris@76
|
220 global $smcFunc, $user_info, $user_profile, $sourcedir, $modSettings, $context, $sc, $user_settings;
|
Chris@76
|
221
|
Chris@76
|
222 // Is OpenID even enabled?
|
Chris@76
|
223 if (empty($modSettings['enableOpenID']))
|
Chris@76
|
224 fatal_lang_error('no_access', false);
|
Chris@76
|
225
|
Chris@76
|
226 if (!isset($_GET['openid_mode']))
|
Chris@76
|
227 fatal_lang_error('openid_return_no_mode', false);
|
Chris@76
|
228
|
Chris@76
|
229 // !!! Check for error status!
|
Chris@76
|
230 if ($_GET['openid_mode'] != 'id_res')
|
Chris@76
|
231 fatal_lang_error('openid_not_resolved');
|
Chris@76
|
232
|
Chris@76
|
233 // SMF has this annoying habit of removing the + from the base64 encoding. So lets put them back.
|
Chris@76
|
234 foreach (array('openid_assoc_handle', 'openid_invalidate_handle', 'openid_sig', 'sf') as $key)
|
Chris@76
|
235 if (isset($_GET[$key]))
|
Chris@76
|
236 $_GET[$key] = str_replace(' ', '+', $_GET[$key]);
|
Chris@76
|
237
|
Chris@76
|
238 // Did they tell us to remove any associations?
|
Chris@76
|
239 if (!empty($_GET['openid_invalidate_handle']))
|
Chris@76
|
240 smf_openid_removeAssociation($_GET['openid_invalidate_handle']);
|
Chris@76
|
241
|
Chris@76
|
242 $server_info = smf_openid_getServerInfo($_GET['openid_identity']);
|
Chris@76
|
243
|
Chris@76
|
244 // Get the association data.
|
Chris@76
|
245 $assoc = smf_openID_getAssociation($server_info['server'], $_GET['openid_assoc_handle'], true);
|
Chris@76
|
246 if ($assoc === null)
|
Chris@76
|
247 fatal_lang_error('openid_no_assoc');
|
Chris@76
|
248
|
Chris@76
|
249 $secret = base64_decode($assoc['secret']);
|
Chris@76
|
250
|
Chris@76
|
251 $signed = explode(',', $_GET['openid_signed']);
|
Chris@76
|
252 $verify_str = '';
|
Chris@76
|
253 foreach ($signed as $sign)
|
Chris@76
|
254 {
|
Chris@76
|
255 $verify_str .= $sign . ':' . strtr($_GET['openid_' . str_replace('.', '_', $sign)], array('&' => '&')) . "\n";
|
Chris@76
|
256 }
|
Chris@76
|
257
|
Chris@76
|
258 $verify_str = base64_encode(sha1_hmac($verify_str, $secret));
|
Chris@76
|
259
|
Chris@76
|
260 if ($verify_str != $_GET['openid_sig'])
|
Chris@76
|
261 {
|
Chris@76
|
262 fatal_lang_error('openid_sig_invalid', 'critical');
|
Chris@76
|
263 }
|
Chris@76
|
264
|
Chris@76
|
265 if (!isset($_SESSION['openid']['saved_data'][$_GET['t']]))
|
Chris@76
|
266 fatal_lang_error('openid_load_data');
|
Chris@76
|
267
|
Chris@76
|
268 $openid_uri = $_SESSION['openid']['saved_data'][$_GET['t']]['openid_uri'];
|
Chris@76
|
269 $modSettings['cookieTime'] = $_SESSION['openid']['saved_data'][$_GET['t']]['cookieTime'];
|
Chris@76
|
270
|
Chris@76
|
271 if (empty($openid_uri))
|
Chris@76
|
272 fatal_lang_error('openid_load_data');
|
Chris@76
|
273
|
Chris@76
|
274 // Any save fields to restore?
|
Chris@76
|
275 $context['openid_save_fields'] = isset($_GET['sf']) ? unserialize(base64_decode($_GET['sf'])) : array();
|
Chris@76
|
276
|
Chris@76
|
277 // Is there a user with this OpenID_uri?
|
Chris@76
|
278 $result = $smcFunc['db_query']('', '
|
Chris@76
|
279 SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt,
|
Chris@76
|
280 openid_uri
|
Chris@76
|
281 FROM {db_prefix}members
|
Chris@76
|
282 WHERE openid_uri = {string:openid_uri}',
|
Chris@76
|
283 array(
|
Chris@76
|
284 'openid_uri' => $openid_uri,
|
Chris@76
|
285 )
|
Chris@76
|
286 );
|
Chris@76
|
287
|
Chris@76
|
288 $member_found = $smcFunc['db_num_rows']($result);
|
Chris@76
|
289
|
Chris@76
|
290 if (!$member_found && isset($_GET['sa']) && $_GET['sa'] == 'change_uri' && !empty($_SESSION['new_openid_uri']) && $_SESSION['new_openid_uri'] == $openid_uri)
|
Chris@76
|
291 {
|
Chris@76
|
292 // Update the member.
|
Chris@76
|
293 updateMemberData($user_settings['id_member'], array('openid_uri' => $openid_uri));
|
Chris@76
|
294
|
Chris@76
|
295 unset($_SESSION['new_openid_uri']);
|
Chris@76
|
296 $_SESSION['openid'] = array(
|
Chris@76
|
297 'verified' => true,
|
Chris@76
|
298 'openid_uri' => $openid_uri,
|
Chris@76
|
299 );
|
Chris@76
|
300
|
Chris@76
|
301 // Send them back to profile.
|
Chris@76
|
302 redirectexit('action=profile;area=authentication;updated');
|
Chris@76
|
303 }
|
Chris@76
|
304 elseif (!$member_found)
|
Chris@76
|
305 {
|
Chris@76
|
306 // Store the received openid info for the user when returned to the registration page.
|
Chris@76
|
307 $_SESSION['openid'] = array(
|
Chris@76
|
308 'verified' => true,
|
Chris@76
|
309 'openid_uri' => $openid_uri,
|
Chris@76
|
310 );
|
Chris@76
|
311 if (isset($_GET['openid_sreg_nickname']))
|
Chris@76
|
312 $_SESSION['openid']['nickname'] = $_GET['openid_sreg_nickname'];
|
Chris@76
|
313 if (isset($_GET['openid_sreg_email']))
|
Chris@76
|
314 $_SESSION['openid']['email'] = $_GET['openid_sreg_email'];
|
Chris@76
|
315 if (isset($_GET['openid_sreg_dob']))
|
Chris@76
|
316 $_SESSION['openid']['dob'] = $_GET['openid_sreg_dob'];
|
Chris@76
|
317 if (isset($_GET['openid_sreg_gender']))
|
Chris@76
|
318 $_SESSION['openid']['gender'] = $_GET['openid_sreg_gender'];
|
Chris@76
|
319
|
Chris@76
|
320 // Were we just verifying the registration state?
|
Chris@76
|
321 if (isset($_GET['sa']) && $_GET['sa'] == 'register2')
|
Chris@76
|
322 {
|
Chris@76
|
323 require_once($sourcedir . '/Register.php');
|
Chris@76
|
324 return Register2(true);
|
Chris@76
|
325 }
|
Chris@76
|
326 else
|
Chris@76
|
327 redirectexit('action=register');
|
Chris@76
|
328 }
|
Chris@76
|
329 elseif (isset($_GET['sa']) && $_GET['sa'] == 'revalidate' && $user_settings['openid_uri'] == $openid_uri)
|
Chris@76
|
330 {
|
Chris@76
|
331 $_SESSION['openid_revalidate_time'] = time();
|
Chris@76
|
332
|
Chris@76
|
333 // Restore the get data.
|
Chris@76
|
334 require_once($sourcedir . '/Subs-Auth.php');
|
Chris@76
|
335 $_SESSION['openid']['saved_data'][$_GET['t']]['get']['openid_restore_post'] = $_GET['t'];
|
Chris@76
|
336 $query_string = construct_query_string($_SESSION['openid']['saved_data'][$_GET['t']]['get']);
|
Chris@76
|
337
|
Chris@76
|
338 redirectexit($query_string);
|
Chris@76
|
339 }
|
Chris@76
|
340 else
|
Chris@76
|
341 {
|
Chris@76
|
342 $user_settings = $smcFunc['db_fetch_assoc']($result);
|
Chris@76
|
343 $smcFunc['db_free_result']($result);
|
Chris@76
|
344
|
Chris@76
|
345 $user_settings['passwd'] = sha1(strtolower($user_settings['member_name']) . $secret);
|
Chris@76
|
346 $user_settings['password_salt'] = substr(md5(mt_rand()), 0, 4);
|
Chris@76
|
347
|
Chris@76
|
348 updateMemberData($user_settings['id_member'], array('passwd' => $user_settings['passwd'], 'password_salt' => $user_settings['password_salt']));
|
Chris@76
|
349
|
Chris@76
|
350 // Cleanup on Aisle 5.
|
Chris@76
|
351 $_SESSION['openid'] = array(
|
Chris@76
|
352 'verified' => true,
|
Chris@76
|
353 'openid_uri' => $openid_uri,
|
Chris@76
|
354 );
|
Chris@76
|
355
|
Chris@76
|
356 require_once($sourcedir . '/LogInOut.php');
|
Chris@76
|
357
|
Chris@76
|
358 if (!checkActivation())
|
Chris@76
|
359 return;
|
Chris@76
|
360
|
Chris@76
|
361 DoLogin();
|
Chris@76
|
362 }
|
Chris@76
|
363 }
|
Chris@76
|
364
|
Chris@76
|
365 function smf_openID_canonize($uri)
|
Chris@76
|
366 {
|
Chris@76
|
367 // !!! Add in discovery.
|
Chris@76
|
368
|
Chris@76
|
369 if (strpos($uri, 'http://') !== 0 && strpos($uri, 'https://') !== 0)
|
Chris@76
|
370 $uri = 'http://' . $uri;
|
Chris@76
|
371
|
Chris@76
|
372 if (strpos(substr($uri, strpos($uri, '://') + 3), '/') === false)
|
Chris@76
|
373 $uri .= '/';
|
Chris@76
|
374
|
Chris@76
|
375 return $uri;
|
Chris@76
|
376 }
|
Chris@76
|
377
|
Chris@76
|
378 function smf_openid_member_exists($url)
|
Chris@76
|
379 {
|
Chris@76
|
380 global $smcFunc;
|
Chris@76
|
381
|
Chris@76
|
382 $request = $smcFunc['db_query']('openid_member_exists', '
|
Chris@76
|
383 SELECT mem.id_member, mem.member_name
|
Chris@76
|
384 FROM {db_prefix}members AS mem
|
Chris@76
|
385 WHERE mem.openid_uri = {string:openid_uri}',
|
Chris@76
|
386 array(
|
Chris@76
|
387 'openid_uri' => $url,
|
Chris@76
|
388 )
|
Chris@76
|
389 );
|
Chris@76
|
390 $member = $smcFunc['db_fetch_assoc']($request);
|
Chris@76
|
391 $smcFunc['db_free_result']($request);
|
Chris@76
|
392
|
Chris@76
|
393 return $member;
|
Chris@76
|
394 }
|
Chris@76
|
395
|
Chris@76
|
396 // Prepare for a Diffie-Hellman key exchange.
|
Chris@76
|
397 function smf_openID_setup_DH($regenerate = false)
|
Chris@76
|
398 {
|
Chris@76
|
399 global $p, $g;
|
Chris@76
|
400
|
Chris@76
|
401 // First off, do we have BC Math available?
|
Chris@76
|
402 if (!function_exists('bcpow'))
|
Chris@76
|
403 return false;
|
Chris@76
|
404
|
Chris@76
|
405 // Defined in OpenID spec.
|
Chris@76
|
406 $p = '155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443';
|
Chris@76
|
407 $g = '2';
|
Chris@76
|
408
|
Chris@76
|
409 // Make sure the scale is set.
|
Chris@76
|
410 bcscale(0);
|
Chris@76
|
411
|
Chris@76
|
412 return smf_openID_get_keys($regenerate);
|
Chris@76
|
413 }
|
Chris@76
|
414
|
Chris@76
|
415 function smf_openID_get_keys($regenerate)
|
Chris@76
|
416 {
|
Chris@76
|
417 global $modSettings, $p, $g;
|
Chris@76
|
418
|
Chris@76
|
419 // Ok lets take the easy way out, are their any keys already defined for us? They are changed in the daily maintenance scheduled task.
|
Chris@76
|
420 if (!empty($modSettings['dh_keys']) && !$regenerate)
|
Chris@76
|
421 {
|
Chris@76
|
422 // Sweeeet!
|
Chris@76
|
423 list ($public, $private) = explode("\n", $modSettings['dh_keys']);
|
Chris@76
|
424 return array(
|
Chris@76
|
425 'public' => base64_decode($public),
|
Chris@76
|
426 'private' => base64_decode($private),
|
Chris@76
|
427 );
|
Chris@76
|
428 }
|
Chris@76
|
429
|
Chris@76
|
430 // 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.
|
Chris@76
|
431 $private = smf_openid_generate_private_key();
|
Chris@76
|
432 $public = bcpowmod($g, $private, $p);
|
Chris@76
|
433
|
Chris@76
|
434 // Now that we did all that work, lets save it so we don't have to keep doing it.
|
Chris@76
|
435 $keys = array('dh_keys' => base64_encode($public) . "\n" . base64_encode($private));
|
Chris@76
|
436 updateSettings($keys);
|
Chris@76
|
437
|
Chris@76
|
438 return array(
|
Chris@76
|
439 'public' => $public,
|
Chris@76
|
440 'private' => $private,
|
Chris@76
|
441 );
|
Chris@76
|
442 }
|
Chris@76
|
443
|
Chris@76
|
444 function smf_openid_generate_private_key()
|
Chris@76
|
445 {
|
Chris@76
|
446 global $p;
|
Chris@76
|
447 static $cache = array();
|
Chris@76
|
448
|
Chris@76
|
449 $byte_string = long_to_binary($p);
|
Chris@76
|
450
|
Chris@76
|
451 if (isset($cache[$byte_string]))
|
Chris@76
|
452 list ($dup, $num_bytes) = $cache[$byte_string];
|
Chris@76
|
453 else
|
Chris@76
|
454 {
|
Chris@76
|
455 $num_bytes = strlen($byte_string) - ($byte_string[0] == "\x00" ? 1 : 0);
|
Chris@76
|
456
|
Chris@76
|
457 $max_rand = bcpow(256, $num_bytes);
|
Chris@76
|
458
|
Chris@76
|
459 $dup = bcmod($max_rand, $num_bytes);
|
Chris@76
|
460
|
Chris@76
|
461 $cache[$byte_string] = array($dup, $num_bytes);
|
Chris@76
|
462 }
|
Chris@76
|
463
|
Chris@76
|
464 do
|
Chris@76
|
465 {
|
Chris@76
|
466 $str = '';
|
Chris@76
|
467 for ($i = 0; $i < $num_bytes; $i += 4)
|
Chris@76
|
468 $str .= pack('L', mt_rand());
|
Chris@76
|
469
|
Chris@76
|
470 $bytes = "\x00" . $str;
|
Chris@76
|
471
|
Chris@76
|
472 $num = binary_to_long($bytes);
|
Chris@76
|
473 } while (bccomp($num, $dup) < 0);
|
Chris@76
|
474
|
Chris@76
|
475 return bcadd(bcmod($num, $p), 1);
|
Chris@76
|
476 }
|
Chris@76
|
477
|
Chris@76
|
478 function smf_openID_getServerInfo($openid_url)
|
Chris@76
|
479 {
|
Chris@76
|
480 global $sourcedir;
|
Chris@76
|
481
|
Chris@76
|
482 require_once($sourcedir . '/Subs-Package.php');
|
Chris@76
|
483
|
Chris@76
|
484 // Get the html and parse it for the openid variable which will tell us where to go.
|
Chris@76
|
485 $webdata = fetch_web_data($openid_url);
|
Chris@76
|
486
|
Chris@76
|
487 if (empty($webdata))
|
Chris@76
|
488 return false;
|
Chris@76
|
489
|
Chris@76
|
490 $response_data = array();
|
Chris@76
|
491
|
Chris@76
|
492 // Some OpenID servers have strange but still valid HTML which makes our job hard.
|
Chris@76
|
493 if (preg_match_all('~<link([\s\S]*?)/?>~i', $webdata, $link_matches) == 0)
|
Chris@76
|
494 fatal_lang_error('openid_server_bad_response');
|
Chris@76
|
495
|
Chris@76
|
496 foreach ($link_matches[1] as $link_match)
|
Chris@76
|
497 {
|
Chris@76
|
498 if (preg_match('~rel="([\s\S]*?)"~i', $link_match, $rel_match) == 0 || preg_match('~href="([\s\S]*?)"~i', $link_match, $href_match) == 0)
|
Chris@76
|
499 continue;
|
Chris@76
|
500
|
Chris@76
|
501 $rels = preg_split('~\s+~', $rel_match[1]);
|
Chris@76
|
502 foreach ($rels as $rel)
|
Chris@76
|
503 if (preg_match('~openid2?\.(server|delegate|provider)~i', $rel, $match) != 0)
|
Chris@76
|
504 $response_data[$match[1]] = $href_match[1];
|
Chris@76
|
505 }
|
Chris@76
|
506
|
Chris@76
|
507 if (empty($response_data['server']))
|
Chris@76
|
508 if (empty($response_data['provider']))
|
Chris@76
|
509 fatal_lang_error('openid_server_bad_response');
|
Chris@76
|
510 else
|
Chris@76
|
511 $response_data['server'] = $response_data['provider'];
|
Chris@76
|
512
|
Chris@76
|
513 return $response_data;
|
Chris@76
|
514 }
|
Chris@76
|
515
|
Chris@76
|
516 function sha1_hmac($data, $key)
|
Chris@76
|
517 {
|
Chris@76
|
518
|
Chris@76
|
519 if (strlen($key) > 64)
|
Chris@76
|
520 $key = sha1_raw($key);
|
Chris@76
|
521
|
Chris@76
|
522 // Pad the key if need be.
|
Chris@76
|
523 $key = str_pad($key, 64, chr(0x00));
|
Chris@76
|
524 $ipad = str_repeat(chr(0x36), 64);
|
Chris@76
|
525 $opad = str_repeat(chr(0x5c), 64);
|
Chris@76
|
526 $hash1 = sha1_raw(($key ^ $ipad) . $data);
|
Chris@76
|
527 $hmac = sha1_raw(($key ^ $opad) . $hash1);
|
Chris@76
|
528 return $hmac;
|
Chris@76
|
529 }
|
Chris@76
|
530
|
Chris@76
|
531 function sha1_raw($text)
|
Chris@76
|
532 {
|
Chris@76
|
533 if (version_compare(PHP_VERSION, '5.0.0') >= 0)
|
Chris@76
|
534 return sha1($text, true);
|
Chris@76
|
535
|
Chris@76
|
536 $hex = sha1($text);
|
Chris@76
|
537 $raw = '';
|
Chris@76
|
538 for ($i = 0; $i < 40; $i += 2)
|
Chris@76
|
539 {
|
Chris@76
|
540 $hexcode = substr($hex, $i, 2);
|
Chris@76
|
541 $charcode = (int) base_convert($hexcode, 16, 10);
|
Chris@76
|
542 $raw .= chr($charcode);
|
Chris@76
|
543 }
|
Chris@76
|
544
|
Chris@76
|
545 return $raw;
|
Chris@76
|
546 }
|
Chris@76
|
547
|
Chris@76
|
548 function binary_to_long($str)
|
Chris@76
|
549 {
|
Chris@76
|
550 $bytes = array_merge(unpack('C*', $str));
|
Chris@76
|
551
|
Chris@76
|
552 $n = 0;
|
Chris@76
|
553
|
Chris@76
|
554 foreach ($bytes as $byte)
|
Chris@76
|
555 {
|
Chris@76
|
556 $n = bcmul($n, 256);
|
Chris@76
|
557 $n = bcadd($n, $byte);
|
Chris@76
|
558 }
|
Chris@76
|
559
|
Chris@76
|
560 return $n;
|
Chris@76
|
561 }
|
Chris@76
|
562
|
Chris@76
|
563 function long_to_binary($value)
|
Chris@76
|
564 {
|
Chris@76
|
565 $cmp = bccomp($value, 0);
|
Chris@76
|
566 if ($cmp < 0)
|
Chris@76
|
567 fatal_error('Only non-negative integers allowed.');
|
Chris@76
|
568
|
Chris@76
|
569 if ($cmp == 0)
|
Chris@76
|
570 return "\x00";
|
Chris@76
|
571
|
Chris@76
|
572 $bytes = array();
|
Chris@76
|
573
|
Chris@76
|
574 while (bccomp($value, 0) > 0)
|
Chris@76
|
575 {
|
Chris@76
|
576 array_unshift($bytes, bcmod($value, 256));
|
Chris@76
|
577 $value = bcdiv($value, 256);
|
Chris@76
|
578 }
|
Chris@76
|
579
|
Chris@76
|
580 if ($bytes && ($bytes[0] > 127))
|
Chris@76
|
581 array_unshift($bytes, 0);
|
Chris@76
|
582
|
Chris@76
|
583 $return = '';
|
Chris@76
|
584 foreach ($bytes as $byte)
|
Chris@76
|
585 $return .= pack('C', $byte);
|
Chris@76
|
586
|
Chris@76
|
587 return $return;
|
Chris@76
|
588 }
|
Chris@76
|
589
|
Chris@76
|
590 function binary_xor($num1, $num2)
|
Chris@76
|
591 {
|
Chris@76
|
592 $return = '';
|
Chris@76
|
593
|
Chris@76
|
594 for ($i = 0; $i < strlen($num2); $i++)
|
Chris@76
|
595 $return .= $num1[$i] ^ $num2[$i];
|
Chris@76
|
596
|
Chris@76
|
597 return $return;
|
Chris@76
|
598 }
|
Chris@76
|
599
|
Chris@76
|
600 // PHP 4 didn't have bcpowmod.
|
Chris@76
|
601 if (!function_exists('bcpowmod') && function_exists('bcpow'))
|
Chris@76
|
602 {
|
Chris@76
|
603 function bcpowmod($num1, $num2, $num3)
|
Chris@76
|
604 {
|
Chris@76
|
605 return bcmod(bcpow($num1, $num2), $num3);
|
Chris@76
|
606 }
|
Chris@76
|
607 }
|
Chris@76
|
608
|
Chris@76
|
609 ?> |