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 https://www.simplemachines.org |
10
|
|
|
* @copyright 2022 Simple Machines and individual contributors |
11
|
|
|
* @license https://www.simplemachines.org/about/smf/license.php BSD |
12
|
|
|
* |
13
|
|
|
* @version 2.1.2 |
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, or |
24
|
|
|
* ends them if cookie_length is less than 0. |
25
|
|
|
* - when logging out, if the globalCookies setting is enabled, attempts to clear the subdomain's |
26
|
|
|
* cookie too. |
27
|
|
|
* |
28
|
|
|
* @param int $cookie_length How many seconds the cookie should last. If negative, forces logout. |
29
|
|
|
* @param int $id The ID of the member to set the cookie for |
30
|
|
|
* @param string $password The hashed password |
31
|
|
|
*/ |
32
|
|
|
function setLoginCookie($cookie_length, $id, $password = '') |
33
|
|
|
{ |
34
|
|
|
global $smcFunc, $cookiename, $boardurl, $modSettings, $sourcedir; |
35
|
|
|
|
36
|
|
|
$id = (int) $id; |
37
|
|
|
|
38
|
|
|
$expiry_time = ($cookie_length >= 0 ? time() + $cookie_length : 1); |
39
|
|
|
|
40
|
|
|
// If changing state force them to re-address some permission caching. |
41
|
|
|
$_SESSION['mc']['time'] = 0; |
42
|
|
|
|
43
|
|
|
// Extract our cookie domain and path from $boardurl |
44
|
|
|
$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); |
45
|
|
|
|
46
|
|
|
// The cookie may already exist, and have been set with different options. |
47
|
|
|
if (isset($_COOKIE[$cookiename])) |
48
|
|
|
{ |
49
|
|
|
// First check for 2.1 json-format cookie |
50
|
|
|
if (preg_match('~^{"0":\d+,"1":"[0-9a-f]*","2":\d+,"3":"[^"]+","4":"[^"]+"~', $_COOKIE[$cookiename]) === 1) |
51
|
|
|
list(,,, $old_domain, $old_path) = $smcFunc['json_decode']($_COOKIE[$cookiename], true); |
52
|
|
|
|
53
|
|
|
// Legacy format (for recent 2.0 --> 2.1 upgrades) |
54
|
|
|
elseif (preg_match('~^a:[34]:\{i:0;i:\d+;i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d+;(i:3;i:\d;)?~', $_COOKIE[$cookiename]) === 1) |
55
|
|
|
{ |
56
|
|
|
list(,,, $old_state) = safe_unserialize($_COOKIE[$cookiename]); |
57
|
|
|
|
58
|
|
|
$cookie_state = (empty($modSettings['localCookies']) ? 0 : 1) | (empty($modSettings['globalCookies']) ? 0 : 2); |
59
|
|
|
|
60
|
|
|
// Maybe we need to temporarily pretend to be using local cookies |
61
|
|
|
if ($cookie_state == 0 && $old_state == 1) |
62
|
|
|
list($old_domain, $old_path) = url_parts(true, false); |
63
|
|
|
else |
64
|
|
|
list($old_domain, $old_path) = url_parts($old_state & 1 > 0, $old_state & 2 > 0); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
// Out with the old, in with the new! |
68
|
|
|
if (isset($old_domain) && $old_domain != $cookie_url[0] || isset($old_path) && $old_path != $cookie_url[1]) |
69
|
|
|
smf_setcookie($cookiename, $smcFunc['json_encode'](array(0, '', 0, $old_domain, $old_path), JSON_FORCE_OBJECT), 1, $old_path, $old_domain); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
// Get the data and path to set it on. |
73
|
|
|
$data = empty($id) ? array(0, '', 0, $cookie_url[0], $cookie_url[1]) : array($id, $password, $expiry_time, $cookie_url[0], $cookie_url[1]); |
74
|
|
|
|
75
|
|
|
// Allow mods to add custom info to the cookie |
76
|
|
|
$custom_data = array(); |
77
|
|
|
call_integration_hook('integrate_cookie_data', array($data, &$custom_data)); |
78
|
|
|
|
79
|
|
|
$data = $smcFunc['json_encode'](array_merge($data, $custom_data), JSON_FORCE_OBJECT); |
80
|
|
|
|
81
|
|
|
// Set the cookie, $_COOKIE, and session variable. |
82
|
|
|
smf_setcookie($cookiename, $data, $expiry_time, $cookie_url[1], $cookie_url[0]); |
83
|
|
|
|
84
|
|
|
// If subdomain-independent cookies are on, unset the subdomain-dependent cookie too. |
85
|
|
|
if (empty($id) && !empty($modSettings['globalCookies'])) |
86
|
|
|
smf_setcookie($cookiename, $data, $expiry_time, $cookie_url[1], ''); |
87
|
|
|
|
88
|
|
|
// Any alias URLs? This is mainly for use with frames, etc. |
89
|
|
|
if (!empty($modSettings['forum_alias_urls'])) |
90
|
|
|
{ |
91
|
|
|
$aliases = explode(',', $modSettings['forum_alias_urls']); |
92
|
|
|
|
93
|
|
|
$temp = $boardurl; |
94
|
|
|
foreach ($aliases as $alias) |
95
|
|
|
{ |
96
|
|
|
// Fake the $boardurl so we can set a different cookie. |
97
|
|
|
$alias = strtr(trim($alias), array('http://' => '', 'https://' => '')); |
98
|
|
|
$boardurl = 'http://' . $alias; |
99
|
|
|
|
100
|
|
|
$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); |
101
|
|
|
|
102
|
|
|
if ($cookie_url[0] == '') |
103
|
|
|
$cookie_url[0] = strtok($alias, '/'); |
104
|
|
|
|
105
|
|
|
$alias_data = $smcFunc['json_decode']($data, true); |
106
|
|
|
$alias_data[3] = $cookie_url[0]; |
107
|
|
|
$alias_data[4] = $cookie_url[1]; |
108
|
|
|
$alias_data = $smcFunc['json_encode']($alias_data, JSON_FORCE_OBJECT); |
109
|
|
|
|
110
|
|
|
smf_setcookie($cookiename, $alias_data, $expiry_time, $cookie_url[1], $cookie_url[0]); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
$boardurl = $temp; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
$_COOKIE[$cookiename] = $data; |
117
|
|
|
|
118
|
|
|
// Make sure the user logs in with a new session ID. |
119
|
|
|
if (!isset($_SESSION['login_' . $cookiename]) || $_SESSION['login_' . $cookiename] !== $data) |
120
|
|
|
{ |
121
|
|
|
// We need to meddle with the session. |
122
|
|
|
require_once($sourcedir . '/Session.php'); |
123
|
|
|
|
124
|
|
|
// Backup and remove the old session. |
125
|
|
|
$oldSessionData = $_SESSION; |
126
|
|
|
$_SESSION = array(); |
127
|
|
|
session_destroy(); |
128
|
|
|
|
129
|
|
|
// Recreate and restore the new session. |
130
|
|
|
loadSession(); |
131
|
|
|
// @todo should we use session_regenerate_id(true); now that we are 5.1+ |
132
|
|
|
session_regenerate_id(); |
133
|
|
|
$_SESSION = $oldSessionData; |
134
|
|
|
|
135
|
|
|
$_SESSION['login_' . $cookiename] = $data; |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Sets Two Factor Auth cookie |
141
|
|
|
* |
142
|
|
|
* @param int $cookie_length How long the cookie should last, in seconds |
143
|
|
|
* @param int $id The ID of the member |
144
|
|
|
* @param string $secret Should be a salted secret using hash_salt |
145
|
|
|
*/ |
146
|
|
|
function setTFACookie($cookie_length, $id, $secret) |
147
|
|
|
{ |
148
|
|
|
global $smcFunc, $modSettings, $cookiename; |
149
|
|
|
|
150
|
|
|
$expiry_time = ($cookie_length >= 0 ? time() + $cookie_length : 1); |
151
|
|
|
|
152
|
|
|
$identifier = $cookiename . '_tfa'; |
153
|
|
|
$cookie_url = url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); |
154
|
|
|
|
155
|
|
|
// Get the data and path to set it on. |
156
|
|
|
$data = $smcFunc['json_encode'](empty($id) ? array(0, '', 0, $cookie_url[0], $cookie_url[1], false) : array($id, $secret, $expiry_time, $cookie_url[0], $cookie_url[1]), JSON_FORCE_OBJECT); |
157
|
|
|
|
158
|
|
|
// Set the cookie, $_COOKIE, and session variable. |
159
|
|
|
smf_setcookie($identifier, $data, $expiry_time, $cookie_url[1], $cookie_url[0]); |
160
|
|
|
|
161
|
|
|
// If subdomain-independent cookies are on, unset the subdomain-dependent cookie too. |
162
|
|
|
if (empty($id) && !empty($modSettings['globalCookies'])) |
163
|
|
|
smf_setcookie($identifier, $data, $expiry_time, $cookie_url[1], ''); |
164
|
|
|
|
165
|
|
|
$_COOKIE[$identifier] = $data; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Get the domain and path for the cookie |
170
|
|
|
* - normally, local and global should be the localCookies and globalCookies settings, respectively. |
171
|
|
|
* - uses boardurl to determine these two things. |
172
|
|
|
* |
173
|
|
|
* @param bool $local Whether we want local cookies |
174
|
|
|
* @param bool $global Whether we want global cookies |
175
|
|
|
* @return array An array to set the cookie on with domain and path in it, in that order |
176
|
|
|
*/ |
177
|
|
|
function url_parts($local, $global) |
178
|
|
|
{ |
179
|
|
|
global $boardurl, $modSettings; |
180
|
|
|
|
181
|
|
|
// Parse the URL with PHP to make life easier. |
182
|
|
|
$parsed_url = parse_iri($boardurl); |
183
|
|
|
|
184
|
|
|
// Is local cookies off? |
185
|
|
|
if (empty($parsed_url['path']) || !$local) |
186
|
|
|
$parsed_url['path'] = ''; |
187
|
|
|
|
188
|
|
|
if (!empty($modSettings['globalCookiesDomain']) && strpos($boardurl, $modSettings['globalCookiesDomain']) !== false) |
189
|
|
|
$parsed_url['host'] = $modSettings['globalCookiesDomain']; |
190
|
|
|
|
191
|
|
|
// Globalize cookies across domains (filter out IP-addresses)? |
192
|
|
|
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) |
193
|
|
|
$parsed_url['host'] = '.' . $parts[1]; |
194
|
|
|
|
195
|
|
|
// We shouldn't use a host at all if both options are off. |
196
|
|
|
elseif (!$local && !$global) |
197
|
|
|
$parsed_url['host'] = ''; |
198
|
|
|
|
199
|
|
|
// The host also shouldn't be set if there aren't any dots in it. |
200
|
|
|
elseif (!isset($parsed_url['host']) || strpos($parsed_url['host'], '.') === false) |
201
|
|
|
$parsed_url['host'] = ''; |
202
|
|
|
|
203
|
|
|
return array($parsed_url['host'], $parsed_url['path'] . '/'); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Throws guests out to the login screen when guest access is off. |
208
|
|
|
* - sets $_SESSION['login_url'] to $_SERVER['REQUEST_URL']. |
209
|
|
|
* - uses the 'kick_guest' sub template found in Login.template.php. |
210
|
|
|
*/ |
211
|
|
|
function KickGuest() |
212
|
|
|
{ |
213
|
|
|
global $txt, $context; |
214
|
|
|
|
215
|
|
|
loadLanguage('Login'); |
216
|
|
|
loadTemplate('Login'); |
217
|
|
|
createToken('login'); |
218
|
|
|
|
219
|
|
|
// Never redirect to an attachment |
220
|
|
|
if (strpos($_SERVER['REQUEST_URL'], 'dlattach') === false) |
221
|
|
|
$_SESSION['login_url'] = $_SERVER['REQUEST_URL']; |
222
|
|
|
|
223
|
|
|
$context['sub_template'] = 'kick_guest'; |
224
|
|
|
$context['page_title'] = $txt['login']; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Display a message about the forum being in maintenance mode. |
229
|
|
|
* - display a login screen with sub template 'maintenance'. |
230
|
|
|
* - sends a 503 header, so search engines don't bother indexing while we're in maintenance mode. |
231
|
|
|
*/ |
232
|
|
|
function InMaintenance() |
233
|
|
|
{ |
234
|
|
|
global $txt, $mtitle, $mmessage, $context, $smcFunc; |
235
|
|
|
|
236
|
|
|
loadLanguage('Login'); |
237
|
|
|
loadTemplate('Login'); |
238
|
|
|
createToken('login'); |
239
|
|
|
|
240
|
|
|
// Send a 503 header, so search engines don't bother indexing while we're in maintenance mode. |
241
|
|
|
send_http_status(503, 'Service Temporarily Unavailable'); |
242
|
|
|
|
243
|
|
|
// Basic template stuff.. |
244
|
|
|
$context['sub_template'] = 'maintenance'; |
245
|
|
|
$context['title'] = $smcFunc['htmlspecialchars']($mtitle); |
246
|
|
|
$context['description'] = &$mmessage; |
247
|
|
|
$context['page_title'] = $txt['maintain_mode']; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Question the verity of the admin by asking for his or her password. |
252
|
|
|
* - loads Login.template.php and uses the admin_login sub template. |
253
|
|
|
* - sends data to template so the admin is sent on to the page they |
254
|
|
|
* wanted if their password is correct, otherwise they can try again. |
255
|
|
|
* |
256
|
|
|
* @param string $type What login type is this - can be 'admin' or 'moderate' |
257
|
|
|
*/ |
258
|
|
|
function adminLogin($type = 'admin') |
259
|
|
|
{ |
260
|
|
|
global $context, $txt, $user_info; |
261
|
|
|
|
262
|
|
|
loadLanguage('Admin'); |
263
|
|
|
loadTemplate('Login'); |
264
|
|
|
|
265
|
|
|
// Validate what type of session check this is. |
266
|
|
|
$types = array(); |
267
|
|
|
call_integration_hook('integrate_validateSession', array(&$types)); |
268
|
|
|
$type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin'; |
269
|
|
|
|
270
|
|
|
// They used a wrong password, log it and unset that. |
271
|
|
|
if (isset($_POST[$type . '_hash_pass']) || isset($_POST[$type . '_pass'])) |
272
|
|
|
{ |
273
|
|
|
$txt['security_wrong'] = sprintf($txt['security_wrong'], isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : $txt['unknown'], $_SERVER['HTTP_USER_AGENT'], $user_info['ip']); |
274
|
|
|
log_error($txt['security_wrong'], 'critical'); |
275
|
|
|
|
276
|
|
|
if (isset($_POST[$type . '_hash_pass'])) |
277
|
|
|
unset($_POST[$type . '_hash_pass']); |
278
|
|
|
if (isset($_POST[$type . '_pass'])) |
279
|
|
|
unset($_POST[$type . '_pass']); |
280
|
|
|
|
281
|
|
|
$context['incorrect_password'] = true; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
createToken('admin-login'); |
285
|
|
|
|
286
|
|
|
// Figure out the get data and post data. |
287
|
|
|
$context['get_data'] = '?' . construct_query_string($_GET); |
288
|
|
|
$context['post_data'] = ''; |
289
|
|
|
|
290
|
|
|
// Now go through $_POST. Make sure the session hash is sent. |
291
|
|
|
$_POST[$context['session_var']] = $context['session_id']; |
292
|
|
|
foreach ($_POST as $k => $v) |
293
|
|
|
$context['post_data'] .= adminLogin_outputPostVars($k, $v); |
294
|
|
|
|
295
|
|
|
// Now we'll use the admin_login sub template of the Login template. |
296
|
|
|
$context['sub_template'] = 'admin_login'; |
297
|
|
|
|
298
|
|
|
// And title the page something like "Login". |
299
|
|
|
if (!isset($context['page_title'])) |
300
|
|
|
$context['page_title'] = $txt['login']; |
301
|
|
|
|
302
|
|
|
// The type of action. |
303
|
|
|
$context['sessionCheckType'] = $type; |
304
|
|
|
|
305
|
|
|
obExit(); |
306
|
|
|
|
307
|
|
|
// We MUST exit at this point, because otherwise we CANNOT KNOW that the user is privileged. |
308
|
|
|
trigger_error('No direct access...', E_USER_ERROR); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* Used by the adminLogin() function. |
313
|
|
|
* if 'value' is an array, the function is called recursively. |
314
|
|
|
* |
315
|
|
|
* @param string $k The keys |
316
|
|
|
* @param string $v The values |
317
|
|
|
* @return string 'hidden' HTML form fields, containing key-value-pairs |
318
|
|
|
*/ |
319
|
|
|
function adminLogin_outputPostVars($k, $v) |
320
|
|
|
{ |
321
|
|
|
global $smcFunc; |
322
|
|
|
|
323
|
|
|
if (!is_array($v)) |
324
|
|
|
return ' |
325
|
|
|
<input type="hidden" name="' . $smcFunc['htmlspecialchars']($k) . '" value="' . strtr($v, array('"' => '"', '<' => '<', '>' => '>')) . '">'; |
326
|
|
|
else |
327
|
|
|
{ |
328
|
|
|
$ret = ''; |
329
|
|
|
foreach ($v as $k2 => $v2) |
330
|
|
|
$ret .= adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2); |
331
|
|
|
|
332
|
|
|
return $ret; |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Properly urlencodes a string to be used in a query |
338
|
|
|
* |
339
|
|
|
* @param string $get |
340
|
|
|
* @return string Our query string |
341
|
|
|
*/ |
342
|
|
|
function construct_query_string($get) |
343
|
|
|
{ |
344
|
|
|
global $scripturl; |
345
|
|
|
|
346
|
|
|
$query_string = ''; |
347
|
|
|
|
348
|
|
|
// Awww, darn. The $scripturl contains GET stuff! |
349
|
|
|
$q = strpos($scripturl, '?'); |
350
|
|
|
if ($q !== false) |
351
|
|
|
{ |
352
|
|
|
parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(substr($scripturl, $q + 1), ';', '&')), $temp); |
353
|
|
|
|
354
|
|
|
foreach ($get as $k => $v) |
355
|
|
|
{ |
356
|
|
|
// Only if it's not already in the $scripturl! |
357
|
|
|
if (!isset($temp[$k])) |
358
|
|
|
$query_string .= urlencode($k) . '=' . urlencode($v) . ';'; |
359
|
|
|
// If it changed, put it out there, but with an ampersand. |
360
|
|
|
elseif ($temp[$k] != $get[$k]) |
361
|
|
|
$query_string .= urlencode($k) . '=' . urlencode($v) . '&'; |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
else |
365
|
|
|
{ |
366
|
|
|
// Add up all the data from $_GET into get_data. |
367
|
|
|
foreach ($get as $k => $v) |
368
|
|
|
$query_string .= urlencode($k) . '=' . urlencode($v) . ';'; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
$query_string = substr($query_string, 0, -1); |
372
|
|
|
return $query_string; |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Finds members by email address, username, or real name. |
377
|
|
|
* - searches for members whose username, display name, or e-mail address match the given pattern of array names. |
378
|
|
|
* - searches only buddies if buddies_only is set. |
379
|
|
|
* |
380
|
|
|
* @param array $names The names of members to search for |
381
|
|
|
* @param bool $use_wildcards Whether to use wildcards. Accepts wildcards ? and * in the pattern if true |
382
|
|
|
* @param bool $buddies_only Whether to only search for the user's buddies |
383
|
|
|
* @param int $max The maximum number of results |
384
|
|
|
* @return array An array containing information about the matching members |
385
|
|
|
*/ |
386
|
|
|
function findMembers($names, $use_wildcards = false, $buddies_only = false, $max = 500) |
387
|
|
|
{ |
388
|
|
|
global $scripturl, $user_info, $smcFunc; |
389
|
|
|
|
390
|
|
|
// If it's not already an array, make it one. |
391
|
|
|
if (!is_array($names)) |
392
|
|
|
$names = explode(',', $names); |
393
|
|
|
|
394
|
|
|
$maybe_email = false; |
395
|
|
|
$names_list = array(); |
396
|
|
|
foreach ($names as $i => $name) |
397
|
|
|
{ |
398
|
|
|
// Trim, and fix wildcards for each name. |
399
|
|
|
$names[$i] = trim($smcFunc['strtolower']($name)); |
400
|
|
|
|
401
|
|
|
$maybe_email |= strpos($name, '@') !== false; |
402
|
|
|
|
403
|
|
|
// Make it so standard wildcards will work. (* and ?) |
404
|
|
|
if ($use_wildcards) |
405
|
|
|
$names[$i] = strtr($names[$i], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '\'' => ''')); |
406
|
|
|
else |
407
|
|
|
$names[$i] = strtr($names[$i], array('\'' => ''')); |
408
|
|
|
|
409
|
|
|
$names_list[] = '{string:lookup_name_' . $i . '}'; |
410
|
|
|
$where_params['lookup_name_' . $i] = $names[$i]; |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
// What are we using to compare? |
414
|
|
|
$comparison = $use_wildcards ? 'LIKE' : '='; |
415
|
|
|
|
416
|
|
|
// Nothing found yet. |
417
|
|
|
$results = array(); |
418
|
|
|
|
419
|
|
|
// This ensures you can't search someones email address if you can't see it. |
420
|
|
|
if (($use_wildcards || $maybe_email) && allowedTo('moderate_forum')) |
421
|
|
|
$email_condition = ' |
422
|
|
|
OR (email_address ' . $comparison . ' \'' . implode('\') OR (email_address ' . $comparison . ' \'', $names) . '\')'; |
423
|
|
|
else |
424
|
|
|
$email_condition = ''; |
425
|
|
|
|
426
|
|
|
// Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise. |
427
|
|
|
$member_name = $smcFunc['db_case_sensitive'] ? 'LOWER(member_name)' : 'member_name'; |
428
|
|
|
$real_name = $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name'; |
429
|
|
|
|
430
|
|
|
// Searches. |
431
|
|
|
$member_name_search = $member_name . ' ' . $comparison . ' ' . implode(' OR ' . $member_name . ' ' . $comparison . ' ', $names_list); |
432
|
|
|
$real_name_search = $real_name . ' ' . $comparison . ' ' . implode(' OR ' . $real_name . ' ' . $comparison . ' ', $names_list); |
433
|
|
|
|
434
|
|
|
// Search by username, display name, and email address. |
435
|
|
|
$request = $smcFunc['db_query']('', ' |
436
|
|
|
SELECT id_member, member_name, real_name, email_address |
437
|
|
|
FROM {db_prefix}members |
438
|
|
|
WHERE (' . $member_name_search . ' |
439
|
|
|
OR ' . $real_name_search . ' ' . $email_condition . ') |
440
|
|
|
' . ($buddies_only ? 'AND id_member IN ({array_int:buddy_list})' : '') . ' |
441
|
|
|
AND is_activated IN (1, 11) |
442
|
|
|
LIMIT {int:limit}', |
443
|
|
|
array_merge($where_params, array( |
|
|
|
|
444
|
|
|
'buddy_list' => $user_info['buddies'], |
445
|
|
|
'limit' => $max, |
446
|
|
|
)) |
447
|
|
|
); |
448
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
449
|
|
|
{ |
450
|
|
|
$results[$row['id_member']] = array( |
451
|
|
|
'id' => $row['id_member'], |
452
|
|
|
'name' => $row['real_name'], |
453
|
|
|
'username' => $row['member_name'], |
454
|
|
|
'email' => allowedTo('moderate_forum') ? $row['email_address'] : '', |
455
|
|
|
'href' => $scripturl . '?action=profile;u=' . $row['id_member'], |
456
|
|
|
'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>' |
457
|
|
|
); |
458
|
|
|
} |
459
|
|
|
$smcFunc['db_free_result']($request); |
460
|
|
|
|
461
|
|
|
// Return all the results. |
462
|
|
|
return $results; |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
/** |
466
|
|
|
* Called by index.php?action=findmember. |
467
|
|
|
* - is used as a popup for searching members. |
468
|
|
|
* - uses sub template find_members of the Help template. |
469
|
|
|
* - also used to add members for PM's sent using wap2/imode protocol. |
470
|
|
|
*/ |
471
|
|
|
function JSMembers() |
472
|
|
|
{ |
473
|
|
|
global $context, $scripturl, $user_info, $smcFunc; |
474
|
|
|
|
475
|
|
|
checkSession('get'); |
476
|
|
|
|
477
|
|
|
// Why is this in the Help template, you ask? Well, erm... it helps you. Does that work? |
478
|
|
|
loadTemplate('Help'); |
479
|
|
|
|
480
|
|
|
$context['template_layers'] = array(); |
481
|
|
|
$context['sub_template'] = 'find_members'; |
482
|
|
|
|
483
|
|
|
if (isset($_REQUEST['search'])) |
484
|
|
|
$context['last_search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES); |
485
|
|
|
else |
486
|
|
|
$_REQUEST['start'] = 0; |
487
|
|
|
|
488
|
|
|
// Allow the user to pass the input to be added to to the box. |
489
|
|
|
$context['input_box_name'] = isset($_REQUEST['input']) && preg_match('~^[\w-]+$~', $_REQUEST['input']) === 1 ? $_REQUEST['input'] : 'to'; |
490
|
|
|
|
491
|
|
|
// Take the delimiter over GET in case it's \n or something. |
492
|
|
|
$context['delimiter'] = isset($_REQUEST['delim']) ? ($_REQUEST['delim'] == 'LB' ? "\n" : $_REQUEST['delim']) : ', '; |
493
|
|
|
$context['quote_results'] = !empty($_REQUEST['quote']); |
494
|
|
|
|
495
|
|
|
// List all the results. |
496
|
|
|
$context['results'] = array(); |
497
|
|
|
|
498
|
|
|
// Some buddy related settings ;) |
499
|
|
|
$context['show_buddies'] = !empty($user_info['buddies']); |
500
|
|
|
$context['buddy_search'] = isset($_REQUEST['buddies']); |
501
|
|
|
|
502
|
|
|
// If the user has done a search, well - search. |
503
|
|
|
if (isset($_REQUEST['search'])) |
504
|
|
|
{ |
505
|
|
|
$_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search'], ENT_QUOTES); |
506
|
|
|
|
507
|
|
|
$context['results'] = findMembers(array($_REQUEST['search']), true, $context['buddy_search']); |
508
|
|
|
$total_results = count($context['results']); |
509
|
|
|
|
510
|
|
|
$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); |
511
|
|
|
|
512
|
|
|
// Determine the navigation context. |
513
|
|
|
$base_url = $scripturl . '?action=findmember;search=' . urlencode($context['last_search']) . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']) . ';' . $context['session_var'] . '=' . $context['session_id']; |
514
|
|
|
$context['links'] = array( |
515
|
|
|
'first' => $_REQUEST['start'] >= 7 ? $base_url . ';start=0' : '', |
516
|
|
|
'prev' => $_REQUEST['start'] >= 7 ? $base_url . ';start=' . ($_REQUEST['start'] - 7) : '', |
517
|
|
|
'next' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . ($_REQUEST['start'] + 7) : '', |
518
|
|
|
'last' => $_REQUEST['start'] + 7 < $total_results ? $base_url . ';start=' . (floor(($total_results - 1) / 7) * 7) : '', |
519
|
|
|
'up' => $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']), |
520
|
|
|
); |
521
|
|
|
$context['page_info'] = array( |
522
|
|
|
'current_page' => $_REQUEST['start'] / 7 + 1, |
523
|
|
|
'num_pages' => floor(($total_results - 1) / 7) + 1 |
524
|
|
|
); |
525
|
|
|
|
526
|
|
|
$context['results'] = array_slice($context['results'], $_REQUEST['start'], 7); |
527
|
|
|
} |
528
|
|
|
else |
529
|
|
|
$context['links']['up'] = $scripturl . '?action=pm;sa=send' . (empty($_REQUEST['u']) ? '' : ';u=' . $_REQUEST['u']); |
530
|
|
|
} |
531
|
|
|
|
532
|
|
|
/** |
533
|
|
|
* Outputs each member name on its own line. |
534
|
|
|
* - used by javascript to find members matching the request. |
535
|
|
|
*/ |
536
|
|
|
function RequestMembers() |
537
|
|
|
{ |
538
|
|
|
global $user_info, $txt, $smcFunc; |
539
|
|
|
|
540
|
|
|
checkSession('get'); |
541
|
|
|
|
542
|
|
|
$_REQUEST['search'] = $smcFunc['htmlspecialchars']($_REQUEST['search']) . '*'; |
543
|
|
|
$_REQUEST['search'] = trim($smcFunc['strtolower']($_REQUEST['search'])); |
544
|
|
|
$_REQUEST['search'] = strtr($_REQUEST['search'], array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_', '&' => '&')); |
545
|
|
|
|
546
|
|
|
if (function_exists('iconv')) |
547
|
|
|
header('content-type: text/plain; charset=UTF-8'); |
548
|
|
|
|
549
|
|
|
$request = $smcFunc['db_query']('', ' |
550
|
|
|
SELECT real_name |
551
|
|
|
FROM {db_prefix}members |
552
|
|
|
WHERE {raw:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? ' |
553
|
|
|
AND id_member IN ({array_int:buddy_list})' : '') . ' |
554
|
|
|
AND is_activated IN (1, 11) |
555
|
|
|
LIMIT ' . ($smcFunc['strlen']($_REQUEST['search']) <= 2 ? '100' : '800'), |
556
|
|
|
array( |
557
|
|
|
'real_name' => $smcFunc['db_case_sensitive'] ? 'LOWER(real_name)' : 'real_name', |
558
|
|
|
'buddy_list' => $user_info['buddies'], |
559
|
|
|
'search' => $_REQUEST['search'], |
560
|
|
|
) |
561
|
|
|
); |
562
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
563
|
|
|
{ |
564
|
|
|
if (function_exists('iconv')) |
565
|
|
|
{ |
566
|
|
|
$utf8 = iconv($txt['lang_character_set'], 'UTF-8', $row['real_name']); |
567
|
|
|
if ($utf8) |
568
|
|
|
$row['real_name'] = $utf8; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
$row['real_name'] = strtr($row['real_name'], array('&' => '&', '<' => '<', '>' => '>', '"' => '"')); |
572
|
|
|
|
573
|
|
|
if (preg_match('~&#\d+;~', $row['real_name']) != 0) |
574
|
|
|
$row['real_name'] = preg_replace_callback('~&#(\d+);~', 'fixchar__callback', $row['real_name']); |
575
|
|
|
|
576
|
|
|
echo $row['real_name'], "\n"; |
577
|
|
|
} |
578
|
|
|
$smcFunc['db_free_result']($request); |
579
|
|
|
|
580
|
|
|
obExit(false); |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
/** |
584
|
|
|
* Generates a random password for a user and emails it to them. |
585
|
|
|
* - called by Profile.php when changing someone's username. |
586
|
|
|
* - checks the validity of the new username. |
587
|
|
|
* - generates and sets a new password for the given user. |
588
|
|
|
* - mails the new password to the email address of the user. |
589
|
|
|
* - if username is not set, only a new password is generated and sent. |
590
|
|
|
* |
591
|
|
|
* @param int $memID The ID of the member |
592
|
|
|
* @param string $username The new username. If set, also checks the validity of the username |
593
|
|
|
*/ |
594
|
|
|
function resetPassword($memID, $username = null) |
595
|
|
|
{ |
596
|
|
|
global $sourcedir, $modSettings, $smcFunc, $language; |
597
|
|
|
|
598
|
|
|
// Language... and a required file. |
599
|
|
|
loadLanguage('Login'); |
600
|
|
|
require_once($sourcedir . '/Subs-Post.php'); |
601
|
|
|
|
602
|
|
|
// Get some important details. |
603
|
|
|
$request = $smcFunc['db_query']('', ' |
604
|
|
|
SELECT member_name, email_address, lngfile |
605
|
|
|
FROM {db_prefix}members |
606
|
|
|
WHERE id_member = {int:id_member}', |
607
|
|
|
array( |
608
|
|
|
'id_member' => $memID, |
609
|
|
|
) |
610
|
|
|
); |
611
|
|
|
list ($user, $email, $lngfile) = $smcFunc['db_fetch_row']($request); |
612
|
|
|
$smcFunc['db_free_result']($request); |
613
|
|
|
|
614
|
|
|
if ($username !== null) |
615
|
|
|
{ |
616
|
|
|
$old_user = $user; |
617
|
|
|
$user = trim($username); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
// Generate a random password. |
621
|
|
|
$newPassword = substr(preg_replace('/\W/', '', md5($smcFunc['random_int']())), 0, 10); |
622
|
|
|
$newPassword_sha1 = hash_password($user, $newPassword); |
623
|
|
|
|
624
|
|
|
// Do some checks on the username if needed. |
625
|
|
|
if ($username !== null) |
626
|
|
|
{ |
627
|
|
|
validateUsername($memID, $user); |
628
|
|
|
|
629
|
|
|
// Update the database... |
630
|
|
|
updateMemberData($memID, array('member_name' => $user, 'passwd' => $newPassword_sha1)); |
631
|
|
|
} |
632
|
|
|
else |
633
|
|
|
updateMemberData($memID, array('passwd' => $newPassword_sha1)); |
634
|
|
|
|
635
|
|
|
call_integration_hook('integrate_reset_pass', array($old_user, $user, $newPassword)); |
636
|
|
|
|
637
|
|
|
$replacements = array( |
638
|
|
|
'USERNAME' => $user, |
639
|
|
|
'PASSWORD' => $newPassword, |
640
|
|
|
); |
641
|
|
|
|
642
|
|
|
$emaildata = loadEmailTemplate('change_password', $replacements, empty($lngfile) || empty($modSettings['userLanguage']) ? $language : $lngfile); |
643
|
|
|
|
644
|
|
|
// Send them the email informing them of the change - then we're done! |
645
|
|
|
sendmail($email, $emaildata['subject'], $emaildata['body'], null, 'chgpass' . $memID, $emaildata['is_html'], 0); |
646
|
|
|
} |
647
|
|
|
|
648
|
|
|
/** |
649
|
|
|
* Checks a username obeys a load of rules |
650
|
|
|
* |
651
|
|
|
* @param int $memID The ID of the member |
652
|
|
|
* @param string $username The username to validate |
653
|
|
|
* @param boolean $return_error Whether to return errors |
654
|
|
|
* @param boolean $check_reserved_name Whether to check this against the list of reserved names |
655
|
|
|
* @return array|null Null if there are no errors, otherwise an array of errors if return_error is true |
656
|
|
|
*/ |
657
|
|
|
function validateUsername($memID, $username, $return_error = false, $check_reserved_name = true) |
658
|
|
|
{ |
659
|
|
|
global $sourcedir, $txt, $smcFunc, $user_info; |
660
|
|
|
|
661
|
|
|
$errors = array(); |
662
|
|
|
|
663
|
|
|
// Don't use too long a name. |
664
|
|
|
if ($smcFunc['strlen']($username) > 25) |
665
|
|
|
$errors[] = array('lang', 'error_long_name'); |
666
|
|
|
|
667
|
|
|
// No name?! How can you register with no name? |
668
|
|
|
if ($username == '') |
669
|
|
|
$errors[] = array('lang', 'need_username'); |
670
|
|
|
|
671
|
|
|
// Only these characters are permitted. |
672
|
|
|
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) |
673
|
|
|
$errors[] = array('lang', 'error_invalid_characters_username'); |
674
|
|
|
|
675
|
|
|
if (stristr($username, $txt['guest_title']) !== false) |
676
|
|
|
$errors[] = array('lang', 'username_reserved', 'general', array($txt['guest_title'])); |
677
|
|
|
|
678
|
|
|
if ($check_reserved_name) |
679
|
|
|
{ |
680
|
|
|
require_once($sourcedir . '/Subs-Members.php'); |
681
|
|
|
if (isReservedName($username, $memID, false)) |
682
|
|
|
$errors[] = array('done', '(' . $smcFunc['htmlspecialchars']($username) . ') ' . $txt['name_in_use']); |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
// Maybe a mod wants to perform more checks? |
686
|
|
|
call_integration_hook('integrate_validate_username', array($username, &$errors)); |
687
|
|
|
|
688
|
|
|
if ($return_error) |
689
|
|
|
return $errors; |
690
|
|
|
elseif (empty($errors)) |
691
|
|
|
return null; |
692
|
|
|
|
693
|
|
|
loadLanguage('Errors'); |
694
|
|
|
$error = $errors[0]; |
695
|
|
|
|
696
|
|
|
$message = $error[0] == 'lang' ? (empty($error[3]) ? $txt[$error[1]] : vsprintf($txt[$error[1]], (array) $error[3])) : $error[1]; |
697
|
|
|
fatal_error($message, empty($error[2]) || $user_info['is_admin'] ? false : $error[2]); |
698
|
|
|
} |
699
|
|
|
|
700
|
|
|
/** |
701
|
|
|
* Checks whether a password meets the current forum rules |
702
|
|
|
* - called when registering/choosing a password. |
703
|
|
|
* - checks the password obeys the current forum settings for password strength. |
704
|
|
|
* - if password checking is enabled, will check that none of the words in restrict_in appear in the password. |
705
|
|
|
* - returns an error identifier if the password is invalid, or null. |
706
|
|
|
* |
707
|
|
|
* @param string $password The desired password |
708
|
|
|
* @param string $username The username |
709
|
|
|
* @param array $restrict_in An array of restricted strings that cannot be part of the password (email address, username, etc.) |
710
|
|
|
* @return null|string Null if valid or a string indicating what the problem was |
711
|
|
|
*/ |
712
|
|
|
function validatePassword($password, $username, $restrict_in = array()) |
713
|
|
|
{ |
714
|
|
|
global $modSettings, $smcFunc; |
715
|
|
|
|
716
|
|
|
// Perform basic requirements first. |
717
|
|
|
if ($smcFunc['strlen']($password) < (empty($modSettings['password_strength']) ? 4 : 8)) |
718
|
|
|
return 'short'; |
719
|
|
|
|
720
|
|
|
// Maybe we need some more fancy password checks. |
721
|
|
|
$pass_error = ''; |
722
|
|
|
call_integration_hook('integrate_validatePassword', array($password, $username, $restrict_in, &$pass_error)); |
723
|
|
|
if (!empty($pass_error)) |
724
|
|
|
return $pass_error; |
725
|
|
|
|
726
|
|
|
// Is this enough? |
727
|
|
|
if (empty($modSettings['password_strength'])) |
728
|
|
|
return null; |
729
|
|
|
|
730
|
|
|
// Otherwise, perform the medium strength test - checking if password appears in the restricted string. |
731
|
|
|
if (preg_match('~\b' . preg_quote($password, '~') . '\b~', implode(' ', $restrict_in)) != 0) |
732
|
|
|
return 'restricted_words'; |
733
|
|
|
elseif ($smcFunc['strpos']($password, $username) !== false) |
734
|
|
|
return 'restricted_words'; |
735
|
|
|
|
736
|
|
|
// If just medium, we're done. |
737
|
|
|
if ($modSettings['password_strength'] == 1) |
738
|
|
|
return null; |
739
|
|
|
|
740
|
|
|
// Otherwise, hard test next, check for numbers and letters, uppercase too. |
741
|
|
|
$good = preg_match('~(\D\d|\d\D)~', $password) != 0; |
742
|
|
|
$good &= $smcFunc['strtolower']($password) != $password; |
743
|
|
|
|
744
|
|
|
return $good ? null : 'chars'; |
745
|
|
|
} |
746
|
|
|
|
747
|
|
|
/** |
748
|
|
|
* Quickly find out what moderation authority this user has |
749
|
|
|
* - builds the moderator, group and board level querys for the user |
750
|
|
|
* - stores the information on the current users moderation powers in $user_info['mod_cache'] and $_SESSION['mc'] |
751
|
|
|
*/ |
752
|
|
|
function rebuildModCache() |
753
|
|
|
{ |
754
|
|
|
global $user_info, $smcFunc; |
755
|
|
|
|
756
|
|
|
// What groups can they moderate? |
757
|
|
|
$group_query = allowedTo('manage_membergroups') ? '1=1' : '0=1'; |
758
|
|
|
|
759
|
|
|
if ($group_query == '0=1' && !$user_info['is_guest']) |
760
|
|
|
{ |
761
|
|
|
$request = $smcFunc['db_query']('', ' |
762
|
|
|
SELECT id_group |
763
|
|
|
FROM {db_prefix}group_moderators |
764
|
|
|
WHERE id_member = {int:current_member}', |
765
|
|
|
array( |
766
|
|
|
'current_member' => $user_info['id'], |
767
|
|
|
) |
768
|
|
|
); |
769
|
|
|
$groups = array(); |
770
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
771
|
|
|
$groups[] = $row['id_group']; |
772
|
|
|
$smcFunc['db_free_result']($request); |
773
|
|
|
|
774
|
|
|
if (empty($groups)) |
775
|
|
|
$group_query = '0=1'; |
776
|
|
|
else |
777
|
|
|
$group_query = 'id_group IN (' . implode(',', $groups) . ')'; |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
// Then, same again, just the boards this time! |
781
|
|
|
$board_query = allowedTo('moderate_forum') ? '1=1' : '0=1'; |
782
|
|
|
|
783
|
|
|
if ($board_query == '0=1' && !$user_info['is_guest']) |
784
|
|
|
{ |
785
|
|
|
$boards = boardsAllowedTo('moderate_board', true); |
786
|
|
|
|
787
|
|
|
if (empty($boards)) |
788
|
|
|
$board_query = '0=1'; |
789
|
|
|
else |
790
|
|
|
$board_query = 'id_board IN (' . implode(',', $boards) . ')'; |
791
|
|
|
} |
792
|
|
|
|
793
|
|
|
// What boards are they the moderator of? |
794
|
|
|
$boards_mod = array(); |
795
|
|
|
if (!$user_info['is_guest']) |
796
|
|
|
{ |
797
|
|
|
$request = $smcFunc['db_query']('', ' |
798
|
|
|
SELECT id_board |
799
|
|
|
FROM {db_prefix}moderators |
800
|
|
|
WHERE id_member = {int:current_member}', |
801
|
|
|
array( |
802
|
|
|
'current_member' => $user_info['id'], |
803
|
|
|
) |
804
|
|
|
); |
805
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
806
|
|
|
$boards_mod[] = $row['id_board']; |
807
|
|
|
$smcFunc['db_free_result']($request); |
808
|
|
|
|
809
|
|
|
// Can any of the groups they're in moderate any of the boards? |
810
|
|
|
$request = $smcFunc['db_query']('', ' |
811
|
|
|
SELECT id_board |
812
|
|
|
FROM {db_prefix}moderator_groups |
813
|
|
|
WHERE id_group IN({array_int:groups})', |
814
|
|
|
array( |
815
|
|
|
'groups' => $user_info['groups'], |
816
|
|
|
) |
817
|
|
|
); |
818
|
|
|
while ($row = $smcFunc['db_fetch_assoc']($request)) |
819
|
|
|
$boards_mod[] = $row['id_board']; |
820
|
|
|
$smcFunc['db_free_result']($request); |
821
|
|
|
|
822
|
|
|
// Just in case we've got duplicates here... |
823
|
|
|
$boards_mod = array_unique($boards_mod); |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
$mod_query = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')'; |
827
|
|
|
|
828
|
|
|
$_SESSION['mc'] = array( |
829
|
|
|
'time' => time(), |
830
|
|
|
// This looks a bit funny but protects against the login redirect. |
831
|
|
|
'id' => $user_info['id'] && $user_info['name'] ? $user_info['id'] : 0, |
832
|
|
|
// If you change the format of 'gq' and/or 'bq' make sure to adjust 'can_mod' in Load.php. |
833
|
|
|
'gq' => $group_query, |
834
|
|
|
'bq' => $board_query, |
835
|
|
|
'ap' => boardsAllowedTo('approve_posts'), |
836
|
|
|
'mb' => $boards_mod, |
837
|
|
|
'mq' => $mod_query, |
838
|
|
|
); |
839
|
|
|
call_integration_hook('integrate_mod_cache'); |
840
|
|
|
|
841
|
|
|
$user_info['mod_cache'] = $_SESSION['mc']; |
842
|
|
|
|
843
|
|
|
// Might as well clean up some tokens while we are at it. |
844
|
|
|
cleanTokens(); |
845
|
|
|
} |
846
|
|
|
|
847
|
|
|
/** |
848
|
|
|
* A wrapper for setcookie that gives integration hook access to it |
849
|
|
|
* |
850
|
|
|
* @param string $name |
851
|
|
|
* @param string $value = '' |
852
|
|
|
* @param int $expire = 0 |
853
|
|
|
* @param string $path = '' |
854
|
|
|
* @param string $domain = '' |
855
|
|
|
* @param bool $secure = false |
856
|
|
|
* @param bool $httponly = true |
857
|
|
|
* @param string $samesite = lax |
858
|
|
|
*/ |
859
|
|
|
function smf_setcookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = null, $httponly = true, $samesite = null) |
860
|
|
|
{ |
861
|
|
|
global $modSettings; |
862
|
|
|
|
863
|
|
|
// In case a customization wants to override the default settings |
864
|
|
|
if ($httponly === null) |
865
|
|
|
$httponly = !empty($modSettings['httponlyCookies']); |
866
|
|
|
if ($secure === null) |
867
|
|
|
$secure = !empty($modSettings['secureCookies']); |
868
|
|
|
if ($samesite === null) |
869
|
|
|
$samesite = !empty($modSettings['samesiteCookies']) ? $modSettings['samesiteCookies'] : 'lax'; |
870
|
|
|
|
871
|
|
|
// Intercept cookie? |
872
|
|
|
call_integration_hook('integrate_cookie', array($name, $value, $expire, $path, $domain, $secure, $httponly, $samesite)); |
873
|
|
|
|
874
|
|
|
if(PHP_VERSION_ID < 70300) |
875
|
|
|
return setcookie($name, $value, $expire, $path . ';samesite=' . $samesite, $domain, $secure, $httponly); |
876
|
|
|
else |
877
|
|
|
return setcookie($name, $value, array( |
878
|
|
|
'expires' => $expire, |
879
|
|
|
'path' => $path, |
880
|
|
|
'domain' => $domain, |
881
|
|
|
'secure' => $secure, |
882
|
|
|
'httponly' => $httponly, |
883
|
|
|
'samesite' => $samesite |
884
|
|
|
)); |
885
|
|
|
} |
886
|
|
|
|
887
|
|
|
/** |
888
|
|
|
* Hashes username with password |
889
|
|
|
* |
890
|
|
|
* @param string $username The username |
891
|
|
|
* @param string $password The unhashed password |
892
|
|
|
* @param int $cost The cost |
893
|
|
|
* @return string The hashed password |
894
|
|
|
*/ |
895
|
|
|
function hash_password($username, $password, $cost = null) |
896
|
|
|
{ |
897
|
|
|
global $smcFunc, $modSettings; |
898
|
|
|
|
899
|
|
|
$cost = empty($cost) ? (empty($modSettings['bcrypt_hash_cost']) ? 10 : $modSettings['bcrypt_hash_cost']) : $cost; |
900
|
|
|
|
901
|
|
|
return password_hash($smcFunc['strtolower']($username) . $password, PASSWORD_BCRYPT, array( |
902
|
|
|
'cost' => $cost, |
903
|
|
|
)); |
904
|
|
|
} |
905
|
|
|
|
906
|
|
|
/** |
907
|
|
|
* Hashes password with salt and authentication secret. This is solely used for cookies. |
908
|
|
|
* |
909
|
|
|
* @param string $password The password |
910
|
|
|
* @param string $salt The salt |
911
|
|
|
* @return string The hashed password |
912
|
|
|
*/ |
913
|
|
|
function hash_salt($password, $salt) |
914
|
|
|
{ |
915
|
|
|
// Append the salt to get a user-specific authentication secret. |
916
|
|
|
$secret_key = get_auth_secret() . $salt; |
917
|
|
|
|
918
|
|
|
// Now use that to generate an HMAC of the password. |
919
|
|
|
return hash_hmac('sha512', $password, $secret_key); |
920
|
|
|
} |
921
|
|
|
|
922
|
|
|
/** |
923
|
|
|
* Verifies a raw SMF password against the bcrypt'd string |
924
|
|
|
* |
925
|
|
|
* @param string $username The username |
926
|
|
|
* @param string $password The password |
927
|
|
|
* @param string $hash The hashed string |
928
|
|
|
* @return bool Whether the hashed password matches the string |
929
|
|
|
*/ |
930
|
|
|
function hash_verify_password($username, $password, $hash) |
931
|
|
|
{ |
932
|
|
|
global $smcFunc; |
933
|
|
|
|
934
|
|
|
return password_verify($smcFunc['strtolower']($username) . $password, $hash); |
935
|
|
|
} |
936
|
|
|
|
937
|
|
|
/** |
938
|
|
|
* Returns the length for current hash |
939
|
|
|
* |
940
|
|
|
* @return int The length for the current hash |
941
|
|
|
*/ |
942
|
|
|
function hash_length() |
943
|
|
|
{ |
944
|
|
|
return 60; |
945
|
|
|
} |
946
|
|
|
|
947
|
|
|
/** |
948
|
|
|
* Benchmarks the server to figure out an appropriate cost factor (minimum 9) |
949
|
|
|
* |
950
|
|
|
* @param float $hashTime Time to target, in seconds |
951
|
|
|
* @return int The cost |
952
|
|
|
*/ |
953
|
|
|
function hash_benchmark($hashTime = 0.2) |
954
|
|
|
{ |
955
|
|
|
$cost = 9; |
956
|
|
|
do |
957
|
|
|
{ |
958
|
|
|
$timeStart = microtime(true); |
959
|
|
|
hash_password('test', 'thisisatestpassword', $cost); |
960
|
|
|
$timeTaken = microtime(true) - $timeStart; |
961
|
|
|
$cost++; |
962
|
|
|
} |
963
|
|
|
while ($timeTaken < $hashTime); |
964
|
|
|
|
965
|
|
|
return $cost; |
966
|
|
|
} |
967
|
|
|
|
968
|
|
|
// Based on code by "examplehash at user dot com". |
969
|
|
|
// https://www.php.net/manual/en/function.hash-equals.php#125034 |
970
|
|
|
if (!function_exists('hash_equals')) |
971
|
|
|
{ |
972
|
|
|
/** |
973
|
|
|
* A compatibility function for when PHP's "hash_equals" function isn't available |
974
|
|
|
* @param string $known_string A known hash |
975
|
|
|
* @param string $user_string The hash of the user string |
976
|
|
|
* @return bool Whether or not the two are equal |
977
|
|
|
*/ |
978
|
|
|
function hash_equals($known_string, $user_string) |
979
|
|
|
{ |
980
|
|
|
$known_string = (string) $known_string; |
981
|
|
|
$user_string = (string) $user_string; |
982
|
|
|
|
983
|
|
|
$sx = 0; |
984
|
|
|
$sy = strlen($known_string); |
985
|
|
|
$uy = strlen($user_string); |
986
|
|
|
$result = $sy - $uy; |
987
|
|
|
for ($ux = 0; $ux < $uy; $ux++) |
988
|
|
|
{ |
989
|
|
|
$result |= ord($user_string[$ux]) ^ ord($known_string[$sx]); |
990
|
|
|
$sx = ($sx + 1) % $sy; |
991
|
|
|
} |
992
|
|
|
|
993
|
|
|
return !$result; |
994
|
|
|
} |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
?> |