Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

Sources/Subs-Auth.php (3 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This file has functions in it to do with authentication, user handling, and the like.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Sets the SMF-style login cookie and session based on the id_member and password passed.
21
 * - password should be already encrypted with the cookie salt.
22
 * - logs the user out if id_member is zero.
23
 * - sets the cookie and session to last the number of seconds specified by cookie_length.
24
 * - when logging out, if the globalCookies setting is enabled, attempts to clear the subdomain's cookie too.
25
 *
26
 * @param int $cookie_length How long the cookie should last (in minutes)
27
 * @param int $id The ID of the member to set the cookie for
28
 * @param string $password The hashed password
29
 */
30
function setLoginCookie($cookie_length, $id, $password = '')
31
{
32
	global $smcFunc, $cookiename, $boardurl, $modSettings, $sourcedir;
33
34
	$id = (int) $id;
35
36
	// If changing state force them to re-address some permission caching.
37
	$_SESSION['mc']['time'] = 0;
38
39
	// The cookie may already exist, and have been set with different options.
40
	$cookie_state = (empty($modSettings['localCookies']) ? 0 : 1) | (empty($modSettings['globalCookies']) ? 0 : 2);
41
	if (isset($_COOKIE[$cookiename]) && preg_match('~^a:[34]:\{i:0;i:\d{1,7};i:1;s:(0|128):"([a-fA-F0-9]{128})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~', $_COOKIE[$cookiename]) === 1)
42
	{
43
		$array = $smcFunc['json_decode']($_COOKIE[$cookiename], true);
44
45
		// Legacy format
46
		if (is_null($array))
47
			$array = safe_unserialize($_COOKIE[$cookiename]);
48
49
		// Out with the old, in with the new!
50
		if (isset($array[3]) && $array[3] != $cookie_state)
51
		{
52
			$cookie_url = url_parts($array[3] & 1 > 0, $array[3] & 2 > 0);
53
			smf_setcookie($cookiename, $smcFunc['json_encode'](array(0, '', 0)), time() - 3600, $cookie_url[1], $cookie_url[0]);
54
		}
55
	}
56
57
	// Get the data and path to set it on.
58
	$data = $smcFunc['json_encode'](empty($id) ? array(0, '', 0) : array($id, $password, time() + $cookie_length, $cookie_state));
59
	$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
60
61
	// Set the cookie, $_COOKIE, and session variable.
62
	smf_setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0]);
63
64
	// If subdomain-independent cookies are on, unset the subdomain-dependent cookie too.
65 View Code Duplication
	if (empty($id) && !empty($modSettings['globalCookies']))
66
		smf_setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], '');
67
68
	// Any alias URLs?  This is mainly for use with frames, etc.
69
	if (!empty($modSettings['forum_alias_urls']))
70
	{
71
		$aliases = explode(',', $modSettings['forum_alias_urls']);
72
73
		$temp = $boardurl;
74
		foreach ($aliases as $alias)
75
		{
76
			// Fake the $boardurl so we can set a different cookie.
77
			$alias = strtr(trim($alias), array('http://' => '', 'https://' => ''));
78
			$boardurl = 'http://' . $alias;
79
80
			$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
81
82
			if ($cookie_url[0] == '')
83
				$cookie_url[0] = strtok($alias, '/');
84
85
			smf_setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0]);
86
		}
87
88
		$boardurl = $temp;
89
	}
90
91
	$_COOKIE[$cookiename] = $data;
92
93
	// Make sure the user logs in with a new session ID.
94
	if (!isset($_SESSION['login_' . $cookiename]) || $_SESSION['login_' . $cookiename] !== $data)
95
	{
96
		// We need to meddle with the session.
97
		require_once($sourcedir . '/Session.php');
98
99
		// Backup and remove the old session.
100
		$oldSessionData = $_SESSION;
101
		$_SESSION = array();
102
		session_destroy();
103
104
		// Recreate and restore the new session.
105
		loadSession();
106
		// @todo should we use session_regenerate_id(true); now that we are 5.1+
107
		session_regenerate_id();
108
		$_SESSION = $oldSessionData;
109
110
		$_SESSION['login_' . $cookiename] = $data;
111
	}
112
}
113
114
/**
115
 * Sets Two Factor Auth cookie
116
 *
117
 * @param int $cookie_length How long the cookie should last, in minutes
118
 * @param int $id The ID of the member
119
 * @param string $secret Should be a salted secret using hash_salt
120
 * @param bool $preserve Whether to preserve the cookie for 30 days
121
 */
122
function setTFACookie($cookie_length, $id, $secret, $preserve = false)
123
{
124
	global $smcFunc, $modSettings, $cookiename;
125
126
	$identifier = $cookiename . '_tfa';
127
	$cookie_state = (empty($modSettings['localCookies']) ? 0 : 1) | (empty($modSettings['globalCookies']) ? 0 : 2);
128
129
	if ($preserve)
130
		$cookie_length = 81600 * 30;
131
132
	// Get the data and path to set it on.
133
	$data = $smcFunc['json_encode'](empty($id) ? array(0, '', 0, $cookie_state, false) : array($id, $secret, time() + $cookie_length, $cookie_state, $preserve));
134
	$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies']));
135
136
	// Set the cookie, $_COOKIE, and session variable.
137
	smf_setcookie($identifier, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0]);
138
139
	// If subdomain-independent cookies are on, unset the subdomain-dependent cookie too.
140 View Code Duplication
	if (empty($id) && !empty($modSettings['globalCookies']))
141
		smf_setcookie($identifier, $data, time() + $cookie_length, $cookie_url[1], '');
142
143
	$_COOKIE[$identifier] = $data;
144
}
145
146
/**
147
 * Get the domain and path for the cookie
148
 * - normally, local and global should be the localCookies and globalCookies settings, respectively.
149
 * - uses boardurl to determine these two things.
150
 *
151
 * @param bool $local Whether we want local cookies
152
 * @param bool $global Whether we want global cookies
153
 * @return array An array to set the cookie on with domain and path in it, in that order
154
 */
155
function url_parts($local, $global)
156
{
157
	global $boardurl, $modSettings;
158
159
	// Parse the URL with PHP to make life easier.
160
	$parsed_url = parse_url($boardurl);
161
162
	// Is local cookies off?
163
	if (empty($parsed_url['path']) || !$local)
164
		$parsed_url['path'] = '';
165
166
	if (!empty($modSettings['globalCookiesDomain']) && strpos($boardurl, $modSettings['globalCookiesDomain']) !== false)
167
		$parsed_url['host'] = $modSettings['globalCookiesDomain'];
168
169
	// Globalize cookies across domains (filter out IP-addresses)?
170
	elseif ($global && preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1)
171
		$parsed_url['host'] = '.' . $parts[1];
172
173
	// We shouldn't use a host at all if both options are off.
174
	elseif (!$local && !$global)
175
		$parsed_url['host'] = '';
176
177
	// The host also shouldn't be set if there aren't any dots in it.
178
	elseif (!isset($parsed_url['host']) || strpos($parsed_url['host'], '.') === false)
179
		$parsed_url['host'] = '';
180
181
	return array($parsed_url['host'], $parsed_url['path'] . '/');
182
}
183
184
/**
185
 * Throws guests out to the login screen when guest access is off.
186
 * - sets $_SESSION['login_url'] to $_SERVER['REQUEST_URL'].
187
 * - uses the 'kick_guest' sub template found in Login.template.php.
188
 */
189
function KickGuest()
190
{
191
	global $txt, $context;
192
193
	loadLanguage('Login');
194
	loadTemplate('Login');
195
	createToken('login');
196
197
	// Never redirect to an attachment
198 View Code Duplication
	if (strpos($_SERVER['REQUEST_URL'], 'dlattach') === false)
199
		$_SESSION['login_url'] = $_SERVER['REQUEST_URL'];
200
201
	$context['sub_template'] = 'kick_guest';
202
	$context['page_title'] = $txt['login'];
203
}
204
205
/**
206
 * Display a message about the forum being in maintenance mode.
207
 * - display a login screen with sub template 'maintenance'.
208
 * - sends a 503 header, so search engines don't bother indexing while we're in maintenance mode.
209
 */
210
function InMaintenance()
211
{
212
	global $txt, $mtitle, $mmessage, $context, $smcFunc;
213
214
	loadLanguage('Login');
215
	loadTemplate('Login');
216
	createToken('login');
217
218
	// Send a 503 header, so search engines don't bother indexing while we're in maintenance mode.
219
	header('HTTP/1.1 503 Service Temporarily Unavailable');
220
221
	// Basic template stuff..
222
	$context['sub_template'] = 'maintenance';
223
	$context['title'] = $smcFunc['htmlspecialchars']($mtitle);
224
	$context['description'] = &$mmessage;
225
	$context['page_title'] = $txt['maintain_mode'];
226
}
227
228
/**
229
 * Question the verity of the admin by asking for his or her password.
230
 * - loads Login.template.php and uses the admin_login sub template.
231
 * - sends data to template so the admin is sent on to the page they
232
 *   wanted if their password is correct, otherwise they can try again.
233
 *
234
 * @param string $type What login type is this - can be 'admin' or 'moderate'
235
 */
236
function adminLogin($type = 'admin')
237
{
238
	global $context, $txt, $user_info;
239
240
	loadLanguage('Admin');
241
	loadTemplate('Login');
242
243
	// Validate what type of session check this is.
244
	$types = array();
245
	call_integration_hook('integrate_validateSession', array(&$types));
246
	$type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin';
247
248
	// They used a wrong password, log it and unset that.
249
	if (isset($_POST[$type . '_hash_pass']) || isset($_POST[$type . '_pass']))
250
	{
251
		$txt['security_wrong'] = sprintf($txt['security_wrong'], isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : $txt['unknown'], $_SERVER['HTTP_USER_AGENT'], $user_info['ip']);
252
		log_error($txt['security_wrong'], 'critical');
253
254
		if (isset($_POST[$type . '_hash_pass']))
255
			unset($_POST[$type . '_hash_pass']);
256
		if (isset($_POST[$type . '_pass']))
257
			unset($_POST[$type . '_pass']);
258
259
		$context['incorrect_password'] = true;
260
	}
261
262
	createToken('admin-login');
263
264
	// Figure out the get data and post data.
265
	$context['get_data'] = '?' . construct_query_string($_GET);
266
	$context['post_data'] = '';
267
268
	// Now go through $_POST.  Make sure the session hash is sent.
269
	$_POST[$context['session_var']] = $context['session_id'];
270
	foreach ($_POST as $k => $v)
271
		$context['post_data'] .= adminLogin_outputPostVars($k, $v);
272
273
	// Now we'll use the admin_login sub template of the Login template.
274
	$context['sub_template'] = 'admin_login';
275
276
	// And title the page something like "Login".
277
	if (!isset($context['page_title']))
278
		$context['page_title'] = $txt['login'];
279
280
	// The type of action.
281
	$context['sessionCheckType'] = $type;
282
283
	obExit();
284
285
	// We MUST exit at this point, because otherwise we CANNOT KNOW that the user is privileged.
286
	trigger_error('Hacking attempt...', E_USER_ERROR);
287
}
288
289
/**
290
 * Used by the adminLogin() function.
291
 * if 'value' is an array, the function is called recursively.
292
 *
293
 * @param string $k The keys
294
 * @param string $v The values
295
 * @return string 'hidden' HTML form fields, containing key-value-pairs
296
 */
297
function adminLogin_outputPostVars($k, $v)
298
{
299
	global $smcFunc;
300
301
	if (!is_array($v))
302
		return '
303
<input type="hidden" name="' . $smcFunc['htmlspecialchars']($k) . '" value="' . strtr($v, array('"' => '&quot;', '<' => '&lt;', '>' => '&gt;')) . '">';
304
	else
305
	{
306
		$ret = '';
307
		foreach ($v as $k2 => $v2)
308
			$ret .= adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2);
309
310
		return $ret;
311
	}
312
}
313
314
/**
315
 * Properly urlencodes a string to be used in a query
316
 *
317
 * @param string $get
318
 * @return string Our query string
319
 */
320
function construct_query_string($get)
321
{
322
	global $scripturl;
323
324
	$query_string = '';
325
326
	// Awww, darn.  The $scripturl contains GET stuff!
327
	$q = strpos($scripturl, '?');
328
	if ($q !== false)
329
	{
330
		parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(substr($scripturl, $q + 1), ';', '&')), $temp);
331
332
		foreach ($get as $k => $v)
0 ignored issues
show
The expression $get of type string is not traversable.
Loading history...
333
		{
334
			// Only if it's not already in the $scripturl!
335
			if (!isset($temp[$k]))
336
				$query_string .= urlencode($k) . '=' . urlencode($v) . ';';
337
			// If it changed, put it out there, but with an ampersand.
338 View Code Duplication
			elseif ($temp[$k] != $get[$k])
339
				$query_string .= urlencode($k) . '=' . urlencode($v) . '&amp;';
340
		}
341
	}
342 View Code Duplication
	else
343
	{
344
		// Add up all the data from $_GET into get_data.
345
		foreach ($get as $k => $v)
0 ignored issues
show
The expression $get of type string is not traversable.
Loading history...
346
			$query_string .= urlencode($k) . '=' . urlencode($v) . ';';
347
	}
348
349
	$query_string = substr($query_string, 0, -1);
350
	return $query_string;
351
}
352
353
/**
354
 * Finds members by email address, username, or real name.
355
 * - searches for members whose username, display name, or e-mail address match the given pattern of array names.
356
 * - searches only buddies if buddies_only is set.
357
 *
358
 * @param array $names The names of members to search for
359
 * @param bool $use_wildcards Whether to use wildcards. Accepts wildcards ? and * in the pattern if true
360
 * @param bool $buddies_only Whether to only search for the user's buddies
361
 * @param int $max The maximum number of results
362
 * @return array An array containing information about the matching members
363
 */
364
function findMembers($names, $use_wildcards = false, $buddies_only = false, $max = 500)
365
{
366
	global $scripturl, $user_info, $smcFunc;
367
368
	// If it's not already an array, make it one.
369
	if (!is_array($names))
370
		$names = explode(',', $names);
371
372
	$maybe_email = false;
373
	foreach ($names as $i => $name)
374
	{
375
		// Trim, and fix wildcards for each name.
376
		$names[$i] = trim($smcFunc['strtolower']($name));
377
378
		$maybe_email |= strpos($name, '@') !== false;
379
380
		// Make it so standard wildcards will work. (* and ?)
381
		if ($use_wildcards)
382
			$names[$i] = strtr($names[$i], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '\'' => '&#039;'));
383
		else
384
			$names[$i] = strtr($names[$i], array('\'' => '&#039;'));
385
	}
386
387
	// What are we using to compare?
388
	$comparison = $use_wildcards ? 'LIKE' : '=';
389
390
	// Nothing found yet.
391
	$results = array();
392
393
	// This ensures you can't search someones email address if you can't see it.
394
	if (($use_wildcards || $maybe_email) && allowedTo('moderate_forum'))
395
		$email_condition = '
396
			OR (email_address ' . $comparison . ' \'' . implode('\') OR (email_address ' . $comparison . ' \'', $names) . '\')';
397
	else
398
		$email_condition = '';
399
400
	// Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise.
401
	$member_name = $smcFunc['db_case_sensitive'] ? 'LOWER(member_name)' : 'member_name';
402
	$real_name = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name';
403
404
	// Search by username, display name, and email address.
405
	$request = $smcFunc['db_query']('', '
406
		SELECT id_member, member_name, real_name, email_address
407
		FROM {db_prefix}members
408
		WHERE ({raw:member_name_search}
409
			OR {raw:real_name_search} {raw:email_condition})
410
			' . ($buddies_only ? 'AND id_member IN ({array_int:buddy_list})' : '') . '
411
			AND is_activated IN (1, 11)
412
		LIMIT {int:limit}',
413
		array(
414
			'buddy_list' => $user_info['buddies'],
415
			'member_name_search' => $member_name . ' ' . $comparison . ' \'' . implode('\' OR ' . $member_name . ' ' . $comparison . ' \'', $names) . '\'',
416
			'real_name_search' => $real_name . ' ' . $comparison . ' \'' . implode('\' OR ' . $real_name . ' ' . $comparison . ' \'', $names) . '\'',
417
			'email_condition' => $email_condition,
418
			'limit' => $max,
419
		)
420
	);
421
	while ($row = $smcFunc['db_fetch_assoc']($request))
422
	{
423
		$results[$row['id_member']] = array(
424
			'id' => $row['id_member'],
425
			'name' => $row['real_name'],
426
			'username' => $row['member_name'],
427
			'email' => allowedTo('moderate_forum') ? $row['email_address'] : '',
428
			'href' => $scripturl . '?action=profile;u=' . $row['id_member'],
429
			'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>'
430
		);
431
	}
432
	$smcFunc['db_free_result']($request);
433
434
	// Return all the results.
435
	return $results;
436
}
437
438
/**
439
 * Called by index.php?action=findmember.
440
 * - is used as a popup for searching members.
441
 * - uses sub template find_members of the Help template.
442
 * - also used to add members for PM's sent using wap2/imode protocol.
443
 */
444
function JSMembers()
445
{
446
	global $context, $scripturl, $user_info, $smcFunc;
447
448
	checkSession('get');
449
450
	// Why is this in the Help template, you ask?  Well, erm... it helps you.  Does that work?
451
	loadTemplate('Help');
452
453
	$context['template_layers'] = array();
454
	$context['sub_template'] = 'find_members';
455
456
	if (isset($_REQUEST['search']))
457
		$context['last_search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES);
458
	else
459
		$_REQUEST['start'] = 0;
460
461
	// Allow the user to pass the input to be added to to the box.
462
	$context['input_box_name'] = isset($_REQUEST['input']) && preg_match('~^[\w-]+$~', $_REQUEST['input']) === 1 ? $_REQUEST['input'] : 'to';
463
464
	// Take the delimiter over GET in case it's \n or something.
465
	$context['delimiter'] = isset($_REQUEST['delim']) ? ($_REQUEST['delim'] == 'LB' ? "\n" : $_REQUEST['delim']) : ', ';
466
	$context['quote_results'] = !empty($_REQUEST['quote']);
467
468
	// List all the results.
469
	$context['results'] = array();
470
471
	// Some buddy related settings ;)
472
	$context['show_buddies'] = !empty($user_info['buddies']);
473
	$context['buddy_search'] = isset($_REQUEST['buddies']);
474
475
	// If the user has done a search, well - search.
476
	if (isset($_REQUEST['search']))
477
	{
478
		$_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES);
479
480
		$context['results'] = findMembers(array($_REQUEST['search']), true, $context['buddy_search']);
481
		$total_results = count($context['results']);
482
483
		$context['page_index'] = constructPageIndex($scripturl . '?action=findmember;search=' . $context['last_search'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';input=' . $context['input_box_name'] . ($context['quote_results'] ? ';quote=1' : '') . ($context['buddy_search'] ? ';buddies' : ''), $_REQUEST['start'], $total_results, 7);
484
485
		// Determine the navigation context.
486
		$base_url = $scripturl . '?action=findmember;search=' . urlencode($context['last_search']) . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']) . ';' . $context['session_var'] . '=' . $context['session_id'];
487
		$context['links'] = array(
488
			'first' => $_REQUEST['start'] >= 7 ? $base_url . ';start=0' : '',
489
			'prev' => $_REQUEST['start'] >= 7 ? $base_url . ';start=' . ($_REQUEST['start'] - 7) : '',
490
			'next' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . ($_REQUEST['start'] + 7) : '',
491
			'last' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . (floor(($total_results - 1) / 7) * 7) : '',
492
			'up' => $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']),
493
		);
494
		$context['page_info'] = array(
495
			'current_page' => $_REQUEST['start'] / 7 + 1,
496
			'num_pages' => floor(($total_results - 1) / 7) + 1
497
		);
498
499
		$context['results'] = array_slice($context['results'], $_REQUEST['start'], 7);
500
	}
501
	else
502
		$context['links']['up'] = $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']);
503
}
504
505
/**
506
 * Outputs each member name on its own line.
507
 * - used by javascript to find members matching the request.
508
 */
509
function RequestMembers()
510
{
511
	global $user_info, $txt, $smcFunc;
512
513
	checkSession('get');
514
515
	$_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search']) . '*';
516
	$_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search']));
517
	$_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&#038;' => '&amp;'));
518
519
	if (function_exists('iconv'))
520
		header('Content-Type: text/plain; charset=UTF-8');
521
522
	$request = $smcFunc['db_query']('', '
523
		SELECT real_name
524
		FROM {db_prefix}members
525
		WHERE {raw:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? '
526
			AND id_member IN ({array_int:buddy_list})' : '') . '
527
			AND is_activated IN (1, 11)
528
		LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'),
529
		array(
530
			'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name',
531
			'buddy_list' => $user_info['buddies'],
532
			'search' => $_REQUEST['search'],
533
		)
534
	);
535
	while ($row = $smcFunc['db_fetch_assoc']($request))
536
	{
537
		if (function_exists('iconv'))
538
		{
539
			$utf8 = iconv($txt['lang_character_set'], 'UTF-8', $row['real_name']);
540
			if ($utf8)
541
				$row['real_name'] = $utf8;
542
		}
543
544
		$row['real_name'] = strtr($row['real_name'], array('&amp;' => '&#038;', '&lt;' => '&#060;', '&gt;' => '&#062;', '&quot;' => '&#034;'));
545
546
		if (preg_match('~&#\d+;~', $row['real_name']) != 0)
547
			$row['real_name'] = preg_replace_callback('~&#(\d+);~', 'fixchar__callback', $row['real_name']);
548
549
		echo $row['real_name'], "\n";
550
	}
551
	$smcFunc['db_free_result']($request);
552
553
	obExit(false);
554
}
555
556
/**
557
 * Generates a random password for a user and emails it to them.
558
 * - called by Profile.php when changing someone's username.
559
 * - checks the validity of the new username.
560
 * - generates and sets a new password for the given user.
561
 * - mails the new password to the email address of the user.
562
 * - if username is not set, only a new password is generated and sent.
563
 *
564
 * @param int $memID The ID of the member
565
 * @param string $username The new username. If set, also checks the validity of the username
566
 */
567
function resetPassword($memID, $username = null)
568
{
569
	global $sourcedir, $modSettings, $smcFunc, $language;
570
571
	// Language... and a required file.
572
	loadLanguage('Login');
573
	require_once($sourcedir . '/Subs-Post.php');
574
575
	// Get some important details.
576
	$request = $smcFunc['db_query']('', '
577
		SELECT member_name, email_address, lngfile
578
		FROM {db_prefix}members
579
		WHERE id_member = {int:id_member}',
580
		array(
581
			'id_member' => $memID,
582
		)
583
	);
584
	list ($user, $email, $lngfile) = $smcFunc['db_fetch_row']($request);
585
	$smcFunc['db_free_result']($request);
586
587
	if ($username !== null)
588
	{
589
		$old_user = $user;
590
		$user = trim($username);
591
	}
592
593
	// Generate a random password.
594
	$newPassword = substr(preg_replace('/\W/', '', md5(mt_rand())), 0, 10);
595
	$newPassword_sha1 = hash_password($user, $newPassword);
596
597
	// Do some checks on the username if needed.
598
	if ($username !== null)
599
	{
600
		validateUsername($memID, $user);
601
602
		// Update the database...
603
		updateMemberData($memID, array('member_name' => $user, 'passwd' => $newPassword_sha1));
604
	}
605
	else
606
		updateMemberData($memID, array('passwd' => $newPassword_sha1));
607
608
	call_integration_hook('integrate_reset_pass', array($old_user, $user, $newPassword));
0 ignored issues
show
The variable $old_user does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
609
610
	$replacements = array(
611
		'USERNAME' => $user,
612
		'PASSWORD' => $newPassword,
613
	);
614
615
	$emaildata = loadEmailTemplate('change_password', $replacements, empty($lngfile) || empty($modSettings['userLanguage']) ? $language : $lngfile);
616
617
	// Send them the email informing them of the change - then we're done!
618
	sendmail($email, $emaildata['subject'], $emaildata['body'], null, 'chgpass' . $memID, $emaildata['is_html'], 0);
619
}
620
621
/**
622
 * Checks a username obeys a load of rules
623
 *
624
 * @param int $memID The ID of the member
625
 * @param string $username The username to validate
626
 * @param boolean $return_error Whether to return errors
627
 * @param boolean $check_reserved_name Whether to check this against the list of reserved names
628
 * @return array|null Null if there are no errors, otherwise an array of errors if return_error is true
629
 */
630
function validateUsername($memID, $username, $return_error = false, $check_reserved_name = true)
631
{
632
	global $sourcedir, $txt, $smcFunc, $user_info;
633
634
	$errors = array();
635
636
	// Don't use too long a name.
637
	if ($smcFunc['strlen']($username) > 25)
638
		$errors[] = array('lang', 'error_long_name');
639
640
	// No name?!  How can you register with no name?
641
	if ($username == '')
642
		$errors[] = array('lang', 'need_username');
643
644
	// Only these characters are permitted.
645
	if (in_array($username, array('_', '|')) || preg_match('~[<>&"\'=\\\\]~', preg_replace('~&#(?:\\d{1,7}|x[0-9a-fA-F]{1,6});~', '', $username)) != 0 || strpos($username, '[code') !== false || strpos($username, '[/code') !== false)
646
		$errors[] = array('lang', 'error_invalid_characters_username');
647
648
	if (stristr($username, $txt['guest_title']) !== false)
649
		$errors[] = array('lang', 'username_reserved', 'general', array($txt['guest_title']));
650
651
	if ($check_reserved_name)
652
	{
653
		require_once($sourcedir . '/Subs-Members.php');
654
		if (isReservedName($username, $memID, false))
655
			$errors[] = array('done', '(' . $smcFunc['htmlspecialchars']($username) . ') ' . $txt['name_in_use']);
656
	}
657
658
	if ($return_error)
659
		return $errors;
660
	elseif (empty($errors))
661
		return null;
662
663
	loadLanguage('Errors');
664
	$error = $errors[0];
665
666
	$message = $error[0] == 'lang' ? (empty($error[3]) ? $txt[$error[1]] : vsprintf($txt[$error[1]], $error[3])) : $error[1];
667
	fatal_error($message, empty($error[2]) || $user_info['is_admin'] ? false : $error[2]);
668
}
669
670
/**
671
 * Checks whether a password meets the current forum rules
672
 * - called when registering/choosing a password.
673
 * - checks the password obeys the current forum settings for password strength.
674
 * - if password checking is enabled, will check that none of the words in restrict_in appear in the password.
675
 * - returns an error identifier if the password is invalid, or null.
676
 *
677
 * @param string $password The desired password
678
 * @param string $username The username
679
 * @param array $restrict_in An array of restricted strings that cannot be part of the password (email address, username, etc.)
680
 * @return null|string Null if valid or a string indicating what the problem was
681
 */
682
function validatePassword($password, $username, $restrict_in = array())
683
{
684
	global $modSettings, $smcFunc;
685
686
	// Perform basic requirements first.
687
	if ($smcFunc['strlen']($password) < (empty($modSettings['password_strength']) ? 4 : 8))
688
		return 'short';
689
690
	// Is this enough?
691
	if (empty($modSettings['password_strength']))
692
		return null;
693
694
	// Otherwise, perform the medium strength test - checking if password appears in the restricted string.
695
	if (preg_match('~\b' . preg_quote($password, '~') . '\b~', implode(' ', $restrict_in)) != 0)
696
		return 'restricted_words';
697
	elseif ($smcFunc['strpos']($password, $username) !== false)
698
		return 'restricted_words';
699
700
	// If just medium, we're done.
701
	if ($modSettings['password_strength'] == 1)
702
		return null;
703
704
	// Otherwise, hard test next, check for numbers and letters, uppercase too.
705
	$good = preg_match('~(\D\d|\d\D)~', $password) != 0;
706
	$good &= $smcFunc['strtolower']($password) != $password;
707
708
	return $good ? null : 'chars';
709
}
710
711
/**
712
 * Quickly find out what moderation authority this user has
713
 * - builds the moderator, group and board level querys for the user
714
 * - stores the information on the current users moderation powers in $user_info['mod_cache'] and $_SESSION['mc']
715
 */
716
function rebuildModCache()
717
{
718
	global $user_info, $smcFunc;
719
720
	// What groups can they moderate?
721
	$group_query = allowedTo('manage_membergroups') ? '1=1' : '0=1';
722
723
	if ($group_query == '0=1' && !$user_info['is_guest'])
724
	{
725
		$request = $smcFunc['db_query']('', '
726
			SELECT id_group
727
			FROM {db_prefix}group_moderators
728
			WHERE id_member = {int:current_member}',
729
			array(
730
				'current_member' => $user_info['id'],
731
			)
732
		);
733
		$groups = array();
734
		while ($row = $smcFunc['db_fetch_assoc']($request))
735
			$groups[] = $row['id_group'];
736
		$smcFunc['db_free_result']($request);
737
738
		if (empty($groups))
739
			$group_query = '0=1';
740
		else
741
			$group_query = 'id_group IN (' . implode(',', $groups) . ')';
742
	}
743
744
	// Then, same again, just the boards this time!
745
	$board_query = allowedTo('moderate_forum') ? '1=1' : '0=1';
746
747
	if ($board_query == '0=1' && !$user_info['is_guest'])
748
	{
749
		$boards = boardsAllowedTo('moderate_board', true);
750
751
		if (empty($boards))
752
			$board_query = '0=1';
753
		else
754
			$board_query = 'id_board IN (' . implode(',', $boards) . ')';
755
	}
756
757
	// What boards are they the moderator of?
758
	$boards_mod = array();
759
	if (!$user_info['is_guest'])
760
	{
761
		$request = $smcFunc['db_query']('', '
762
			SELECT id_board
763
			FROM {db_prefix}moderators
764
			WHERE id_member = {int:current_member}',
765
			array(
766
				'current_member' => $user_info['id'],
767
			)
768
		);
769
		while ($row = $smcFunc['db_fetch_assoc']($request))
770
			$boards_mod[] = $row['id_board'];
771
		$smcFunc['db_free_result']($request);
772
773
		// Can any of the groups they're in moderate any of the boards?
774
		$request = $smcFunc['db_query']('', '
775
			SELECT id_board
776
			FROM {db_prefix}moderator_groups
777
			WHERE id_group IN({array_int:groups})',
778
			array(
779
				'groups' => $user_info['groups'],
780
			)
781
		);
782
		while ($row = $smcFunc['db_fetch_assoc']($request))
783
			$boards_mod[] = $row['id_board'];
784
		$smcFunc['db_free_result']($request);
785
786
		// Just in case we've got duplicates here...
787
		$boards_mod = array_unique($boards_mod);
788
	}
789
790
	$mod_query = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')';
791
792
	$_SESSION['mc'] = array(
793
		'time' => time(),
794
		// This looks a bit funny but protects against the login redirect.
795
		'id' => $user_info['id'] && $user_info['name'] ? $user_info['id'] : 0,
796
		// If you change the format of 'gq' and/or 'bq' make sure to adjust 'can_mod' in Load.php.
797
		'gq' => $group_query,
798
		'bq' => $board_query,
799
		'ap' => boardsAllowedTo('approve_posts'),
800
		'mb' => $boards_mod,
801
		'mq' => $mod_query,
802
	);
803
	call_integration_hook('integrate_mod_cache');
804
805
	$user_info['mod_cache'] = $_SESSION['mc'];
806
807
	// Might as well clean up some tokens while we are at it.
808
	cleanTokens();
809
}
810
811
/**
812
 * The same thing as setcookie but gives support for HTTP-Only cookies in PHP < 5.2
813
 * @todo We can remove this since SMF requires PHP >= 5.3.8 now
814
 *
815
 * @param string $name
816
 * @param string $value = ''
817
 * @param int $expire = 0
818
 * @param string $path = ''
819
 * @param string $domain = ''
820
 * @param bool $secure = false
821
 * @param bool $httponly = true
822
 */
823
function smf_setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = null, $httponly = true)
824
{
825
	global $modSettings;
826
827
	// In case a customization wants to override the default settings
828
	if ($httponly === null)
829
		$httponly = !empty($modSettings['httponlyCookies']);
830
	if ($secure === null)
831
		$secure = !empty($modSettings['secureCookies']);
832
833
	// Intercept cookie?
834
	call_integration_hook('integrate_cookie', array($name, $value, $expire, $path, $domain, $secure, $httponly));
835
836
	// This function is pointless if we have PHP >= 5.2.
837
	return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
838
}
839
840
/**
841
 * Hashes username with password
842
 *
843
 * @param string $username The username
844
 * @param string $password The unhashed password
845
 * @param int $cost The cost
846
 * @return string The hashed password
847
 */
848
function hash_password($username, $password, $cost = null)
849
{
850
	global $sourcedir, $smcFunc, $modSettings;
851
	if (!function_exists('password_hash'))
852
		require_once($sourcedir . '/Subs-Password.php');
853
854
	$cost = empty($cost) ? (empty($modSettings['bcrypt_hash_cost']) ? 10 : $modSettings['bcrypt_hash_cost']) : $cost;
855
856
	return password_hash($smcFunc['strtolower']($username) . $password, PASSWORD_BCRYPT, array(
857
		'cost' => $cost,
858
	));
859
}
860
861
/**
862
 * Hashes password with salt, this is solely used for cookies.
863
 *
864
 * @param string $password The password
865
 * @param string $salt The salt
866
 * @return string The hashed password
867
 */
868
function hash_salt($password, $salt)
869
{
870
	return hash('sha512', $password . $salt);
871
}
872
873
/**
874
 * Verifies a raw SMF password against the bcrypt'd string
875
 *
876
 * @param string $username The username
877
 * @param string $password The password
878
 * @param string $hash The hashed string
879
 * @return bool Whether the hashed password matches the string
880
 */
881
function hash_verify_password($username, $password, $hash)
882
{
883
	global $sourcedir, $smcFunc;
884
	if (!function_exists('password_verify'))
885
		require_once($sourcedir . '/Subs-Password.php');
886
887
	return password_verify($smcFunc['strtolower']($username) . $password, $hash);
888
}
889
890
/**
891
 * Returns the length for current hash
892
 *
893
 * @return int The length for the current hash
894
 */
895
function hash_length()
896
{
897
	return 60;
898
}
899
900
/**
901
 * Benchmarks the server to figure out an appropriate cost factor (minimum 9)
902
 *
903
 * @param float $hashTime Time to target, in seconds
904
 * @return int The cost
905
 */
906
function hash_benchmark($hashTime = 0.2)
907
{
908
	$cost = 9;
909
	do {
910
		$timeStart = microtime(true);
911
		hash_password('test', 'thisisatestpassword', $cost);
912
		$timeTaken = microtime(true) - $timeStart;
913
		$cost++;
914
	} while ($timeTaken < $hashTime);
915
916
	return $cost;
917
}
918
919
?>