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

Subs-Auth.php ➔ hash_salt()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
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+
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
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)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $k. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $v. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
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, '?');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $q. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
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
Bug introduced by
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
Bug introduced by
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
0 ignored issues
show
Documentation introduced by
Should the type for parameter $username not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
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
Bug introduced by
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
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the type for parameter $secure not be boolean|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cost not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
846
 * @return string The hashed password
0 ignored issues
show
Documentation introduced by
Should the return type not be null|false|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
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
?>